Improve ISO installer non-GPU and bundled setup flow
This commit is contained in:
1 parent
82cd52a409
commit
ec296b3d42
7 files changed
+148
-25
No files matched your search
@@ -13,6 +13,7 @@
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PACKAGE_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
WORK_DIR="/tmp/cezen-iso-work"
|
||||
ORIGINAL_ISO="/tmp/ubuntu-22.04.5-live-server-amd64.iso"
|
||||
OUTPUT_ISO="$SCRIPT_DIR/cezen-ai-ubuntu2204.iso"
|
||||
@@ -26,7 +27,7 @@ echo ""
|
||||
# ── Install build tools ────────────────────────
|
||||
echo "→ Installing build tools..."
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq xorriso wget isolinux
|
||||
apt-get install -y -qq xorriso wget isolinux rsync
|
||||
echo "✓ Tools ready"
|
||||
|
||||
# ── Download Ubuntu ISO ────────────────────────
|
||||
@@ -55,6 +56,16 @@ cp "$SCRIPT_DIR/user-data" "$WORK_DIR/nocloud/user-data"
|
||||
cp "$SCRIPT_DIR/meta-data" "$WORK_DIR/nocloud/meta-data"
|
||||
echo "✓ user-data and meta-data injected"
|
||||
|
||||
# Keep the installer payload on the ISO so first boot does not depend on a
|
||||
# private Git server being reachable before the setup UI can start.
|
||||
echo "→ Bundling Cezen AI installer payload..."
|
||||
mkdir -p "$WORK_DIR/cezen-aipackage"
|
||||
rsync -a --delete \
|
||||
--exclude 'autoinstall/cezen-ai-ubuntu2204.iso' \
|
||||
--exclude '*.iso' \
|
||||
"$PACKAGE_DIR/" "$WORK_DIR/cezen-aipackage/"
|
||||
echo "✓ Installer payload bundled"
|
||||
|
||||
# ── Patch GRUB ────────────────────────────────
|
||||
echo "→ Patching GRUB config..."
|
||||
GRUB_CFG="$WORK_DIR/boot/grub/grub.cfg"
|
||||
|
||||
@@ -169,18 +169,22 @@ 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
|
||||
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
|
||||
|
||||
# Mark as configured so this wizard doesn't run again
|
||||
touch /opt/cezen/.setup-done
|
||||
|
||||
|
||||
@@ -60,8 +60,10 @@ autoinstall:
|
||||
- echo "cezen ALL=(ALL) NOPASSWD:ALL" > /target/etc/sudoers.d/cezen
|
||||
- chmod 440 /target/etc/sudoers.d/cezen
|
||||
|
||||
# Clone the Cezen AI installer
|
||||
- git clone https://cgit.cezentech.com/jinojose/aipackage.git /target/opt/aipackage || true
|
||||
# Install the Cezen AI payload from the ISO first. Fall back to Git only
|
||||
# when building from older media that does not contain /cdrom/cezen-aipackage.
|
||||
- mkdir -p /target/opt/aipackage
|
||||
- cp -a /cdrom/cezen-aipackage/. /target/opt/aipackage/ || git clone https://cgit.cezentech.com/jinojose/aipackage.git /target/opt/aipackage
|
||||
|
||||
# Deploy the web setup server
|
||||
- mkdir -p /target/opt/cezen
|
||||
|
||||
@@ -36,7 +36,30 @@ def get_interfaces():
|
||||
except:
|
||||
return ["eth0"]
|
||||
|
||||
def has_nvidia_gpu():
|
||||
"""Detect NVIDIA PCI devices before the driver or nvidia-smi exists."""
|
||||
try:
|
||||
for root, _, files in os.walk("/sys/bus/pci/devices"):
|
||||
if "vendor" not in files:
|
||||
continue
|
||||
with open(os.path.join(root, "vendor")) as f:
|
||||
if f.read().strip().lower() == "0x10de":
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
def validate_static_network(ip, prefix, gateway, dns):
|
||||
ipaddress.ip_address(ip)
|
||||
ipaddress.ip_address(gateway)
|
||||
ipaddress.ip_address(dns)
|
||||
prefix_int = int(prefix)
|
||||
if prefix_int < 1 or prefix_int > 32:
|
||||
raise ValueError("CIDR prefix must be between 1 and 32")
|
||||
return str(prefix_int)
|
||||
|
||||
def apply_static_ip(iface, ip, prefix, gateway, dns):
|
||||
prefix = validate_static_network(ip, prefix, gateway, dns)
|
||||
config = f"""network:
|
||||
version: 2
|
||||
ethernets:
|
||||
@@ -69,9 +92,10 @@ def run_install(tier, skip_tools):
|
||||
open(SETUP_DONE_FILE, "w").close()
|
||||
|
||||
env = os.environ.copy()
|
||||
# Phase 1: installs NVIDIA drivers, registers cezen-phase2 systemd service,
|
||||
# then reboots. Phase 2 (full stack) runs automatically after reboot.
|
||||
cmd = ["bash", f"{AIPACKAGE_DIR}/install.sh", "--phase=1", f"--tier={tier}"]
|
||||
# Fresh NVIDIA servers do not have nvidia-smi yet, so detect the PCI
|
||||
# device and run phase 1 to install drivers before the AI stack.
|
||||
phase = "1" if has_nvidia_gpu() else "2"
|
||||
cmd = ["bash", f"{AIPACKAGE_DIR}/install.sh", f"--phase={phase}", f"--tier={tier}"]
|
||||
with open(INSTALL_LOG, "w") as log:
|
||||
proc = subprocess.Popen(cmd, stdout=log, stderr=log, env=env)
|
||||
proc.wait()
|
||||
@@ -525,11 +549,18 @@ function streamLog() {
|
||||
}
|
||||
};
|
||||
|
||||
let reconnectAttempts = 0;
|
||||
es.onerror = () => {
|
||||
es.close();
|
||||
if (!installDone) {
|
||||
// Connection lost — most likely the server rebooted
|
||||
if (installDone) return;
|
||||
reconnectAttempts++;
|
||||
lbl.textContent = `Connection lost — reconnecting... (${reconnectAttempts})`;
|
||||
if (reconnectAttempts >= 5) {
|
||||
// After 5 failed reconnects assume it's a real reboot
|
||||
showRebootNotice();
|
||||
} else {
|
||||
// Try reconnecting after a delay
|
||||
setTimeout(() => { if (!installDone) streamLog(); }, 4000);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -653,14 +684,46 @@ class Handler(BaseHTTPRequestHandler):
|
||||
self.end_headers()
|
||||
|
||||
# ─── Main ─────────────────────────────────────────────────
|
||||
def show_console_banner(ip):
|
||||
"""Write the setup URL banner to /dev/tty1 so it appears on the physical console."""
|
||||
banner = f"""
|
||||
|
||||
\033[1;36m╔══════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ CEZEN AI SUITE — SERVER SETUP ║
|
||||
║ ║
|
||||
║ Open a browser on any computer on this network: ║
|
||||
║ ║
|
||||
║ \033[1;33m➜ http://{ip:<42}\033[1;36m║
|
||||
║ \033[1;33m➜ http://cezenai.local\033[1;36m ║
|
||||
║ ║
|
||||
║ Complete setup from your browser — no keyboard ║
|
||||
║ input needed here. ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════╝\033[0m
|
||||
|
||||
"""
|
||||
# Write to tty1 (physical console) and stdout (journalctl)
|
||||
print(banner)
|
||||
try:
|
||||
with open("/dev/tty1", "w") as tty:
|
||||
tty.write(banner)
|
||||
except Exception:
|
||||
pass # tty1 may not be accessible in all environments
|
||||
|
||||
# Also update /etc/issue so the URL appears above the login prompt
|
||||
try:
|
||||
with open("/etc/issue", "w") as f:
|
||||
f.write(f"Ubuntu 22.04.5 LTS \\n \\l\n\n")
|
||||
f.write(f" \033[1;36mCezen AI Suite Setup:\033[0m http://{ip} | http://cezenai.local\n\n")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Ensure log file exists
|
||||
open(INSTALL_LOG, "a").close()
|
||||
ip = get_ip()
|
||||
print(f"\n{'='*50}")
|
||||
print(f" Cezen AI Suite — Setup Server")
|
||||
print(f" Open in browser: http://{ip}")
|
||||
print(f" Or: http://cezenai.local")
|
||||
print(f"{'='*50}\n")
|
||||
show_console_banner(ip)
|
||||
server = HTTPServer(("0.0.0.0", 80), Handler)
|
||||
server.serve_forever()
|
||||
Reference in new issue
Block a user