aipackage/autoinstall/firstboot-setup.sh
2026-06-30 11:40:05 +05:30

266 lines
11 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────
# Nexus One AI — 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"
export TERM="${TERM:-linux}"
exec > >(tee -a "$LOG_FILE") 2>&1
detect_iface() {
ip route show default 2>/dev/null | awk '/default/ {print $5; exit}'
}
IFACE="$(detect_iface)"
IFACE="${IFACE:-$(ip -o link show | awk -F': ' '$2 !~ /lo|docker|br-|veth/ {print $2; exit}')}"
# ── Colors / terminal setup ────────────────────────────────
export NEWT_COLORS='
root=,black
window=black,white
border=white,black
title=black,white
button=black,white
actbutton=white,blue
checkbox=black,white
actcheckbox=white,blue
entry=black,white
label=black,white
listbox=black,white
actlistbox=white,blue
textbox=black,white
acttextbox=white,blue
'
TITLE=" Nexus One AI — Server Setup "
H=20
W=70
# ── Welcome ────────────────────────────────────────────────
whiptail --title "$TITLE" \
--msgbox "\nWelcome to the Nexus One AI installer.\n\nThis wizard will configure your network, capture license/customer details, 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 4: 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
rm -f /etc/netplan/99-cezen-dhcp.yaml || true
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 — persist it explicitly and confirm it's working
rm -f /etc/netplan/99-cezen-static.yaml || true
cat > /etc/netplan/99-cezen-dhcp.yaml << EOF
network:
version: 2
ethernets:
${IFACE}:
dhcp4: true
EOF
netplan apply 2>/dev/null || true
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 switch to a static IP later by editing:\n /etc/netplan/99-cezen-dhcp.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: LICENSE / CUSTOMER DETAILS
# ════════════════════════════════════════════════════════════
CUSTOMER_NAME=$(whiptail --title "$TITLE" \
--inputbox "\nStep 2 of 4: License & Customer Details\n\nCustomer / organisation name:" \
$H $W "" 3>&1 1>&2 2>&3)
CUSTOMER_ID=$(whiptail --title "$TITLE" \
--inputbox "\nStep 2 of 4: License & Customer Details\n\nCustomer ID / project code:\n\nLeave blank for evaluation or field staging." \
$H $W "" 3>&1 1>&2 2>&3)
CONTACT_EMAIL=$(whiptail --title "$TITLE" \
--inputbox "\nStep 2 of 4: License & Customer Details\n\nCustomer/admin contact email:\n\nLeave blank if unavailable on site." \
$H $W "" 3>&1 1>&2 2>&3)
LICENSE_KEY=$(whiptail --title "$TITLE" \
--passwordbox "\nStep 2 of 4: License & Customer Details\n\nLicense key / activation code:\n\nLeave blank for offline field staging. Models and keys can be added later." \
$H $W "" 3>&1 1>&2 2>&3)
SUPPORT_UNTIL=$(whiptail --title "$TITLE" \
--inputbox "\nStep 2 of 4: License & Customer Details\n\nSupport valid until (YYYY-MM-DD):\n\nLeave blank if not issued yet." \
$H $W "" 3>&1 1>&2 2>&3)
# ════════════════════════════════════════════════════════════
# STEP 3: SELECT TIER
# ════════════════════════════════════════════════════════════
TIER=$(whiptail --title "$TITLE" \
--menu "\nStep 3 of 4: Select AI Package Tier\n\nChoose the tier that matches your hardware:" \
$H $W 4 \
"starter" "Starter — 1× RTX 5090 / 32GB VRAM · Small team" \
"basic" "Entry — 1× NVIDIA RTX Pro 6000 (96GB) · Up to 20 users" \
"pro" "Pro — 2× RTX 5090 / RTX Pro class · Up to 100 users" \
"max" "Max — 48× H100/H200/A100 class · 100+ users" \
3>&1 1>&2 2>&3)
# ════════════════════════════════════════════════════════════
# STEP 4: SELECT AI TOOLS
# ════════════════════════════════════════════════════════════
TOOLS=$(whiptail --title "$TITLE" \
--checklist "\nStep 4 of 4: 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}')
LICENSE_DISPLAY="Field staging / evaluation"
if [ -n "$LICENSE_KEY" ]; then
LICENSE_DISPLAY="Provided"
fi
whiptail --title "$TITLE" \
--yesno "\nReady to install. Please confirm:\n\nNetwork: ${NET_MODE} (${MY_IP})\nCustomer: ${CUSTOMER_NAME:-Not entered}\nLicense: ${LICENSE_DISPLAY}\nTier: ${TIER}\n\nTools:\n${TOOLS_DISPLAY}\n\nThis will take 2040 minutes.\nThe server will reboot once during install (NVIDIA drivers).\n\nContinue?" \
$H $W
# ════════════════════════════════════════════════════════════
# RUN INSTALLER
# ════════════════════════════════════════════════════════════
clear
echo ""
echo "╔══════════════════════════════════════════╗"
echo "║ Nexus One AI — 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
SKIP_ROLES=""
for role in ollama jupyterlab chromadb vllm mlflow minio monitoring k3s; do
if ! echo "$TOOLS" | grep -q "$role"; then
if [ -n "$SKIP_ROLES" ]; then
SKIP_ROLES="${SKIP_ROLES},${role}"
else
SKIP_ROLES="${role}"
fi
fi
done
cat > /opt/cezen/install.conf << EOF
TIER=${TIER}
SKIP_ROLES=${SKIP_ROLES}
EOF
export CUSTOMER_NAME CUSTOMER_ID CONTACT_EMAIL LICENSE_KEY SUPPORT_UNTIL TIER
python3 - <<'PY'
import json, os, time
payload = {
"schema": "cezen.license.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(),
"license_key": os.environ.get("LICENSE_KEY", "").strip(),
"tier": os.environ.get("TIER", "basic").strip(),
"support_until": os.environ.get("SUPPORT_UNTIL", "").strip(),
"install_type": "licensed" if os.environ.get("LICENSE_KEY", "").strip() else "field-staging",
"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)
PY
chown root:cezen /opt/cezen/license.json 2>/dev/null || true
chmod 0640 /opt/cezen/license.json
# 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"
whiptail --title "$TITLE" \
--msgbox "\nInstaller command finished.\n\nFor detailed logs, run:\n sudo journalctl -u cezen-phase2.service -f\n sudo tail -f /var/log/cezen-install.log" \
$H $W