diff --git a/ansible/entry.yml b/ansible/entry.yml index 5ae2a4f..5fbad77 100644 --- a/ansible/entry.yml +++ b/ansible/entry.yml @@ -10,15 +10,26 @@ cezen_home: "/opt/cezen" python_version: "3.11" cuda_version: "12.4" + skip_roles: "" # comma-separated list of role names to skip (set by install.sh) roles: - - base - - docker - - k3s - - ollama - - vllm - - jupyterlab - - chromadb - - mlflow - - minio - - monitoring + - role: base + when: "'base' not in skip_roles.split(',')" + - role: docker + when: "'docker' not in skip_roles.split(',')" + - role: k3s + when: "'k3s' not in skip_roles.split(',')" + - role: ollama + when: "'ollama' not in skip_roles.split(',')" + - role: vllm + when: "'vllm' not in skip_roles.split(',')" + - role: jupyterlab + when: "'jupyterlab' not in skip_roles.split(',')" + - role: chromadb + when: "'chromadb' not in skip_roles.split(',')" + - role: mlflow + when: "'mlflow' not in skip_roles.split(',')" + - role: minio + when: "'minio' not in skip_roles.split(',')" + - role: monitoring + when: "'monitoring' not in skip_roles.split(',')" diff --git a/autoinstall/user-data b/autoinstall/user-data index 1a9da85..60467ab 100644 --- a/autoinstall/user-data +++ b/autoinstall/user-data @@ -8,7 +8,7 @@ autoinstall: layout: us # ── Network: DHCP on first ethernet ─────────── - # Final network config is set by firstboot-setup.sh wizard + # Final network config is set by the web setup UI (browser on port 80) network: network: version: 2 @@ -46,8 +46,10 @@ autoinstall: - git - curl - wget - - whiptail - net-tools + - python3 + - python3-pip + - avahi-daemon # ── Late commands ───────────────────────────── late-commands: @@ -62,35 +64,36 @@ autoinstall: # 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 + # Deploy the web setup server + - mkdir -p /target/opt/cezen + - cp /target/opt/aipackage/autoinstall/websetup/server.py /target/opt/cezen/websetup.py + - chmod +x /target/opt/cezen/websetup.py - # Create firstboot systemd service (runs the TUI wizard on first login) + # Set hostname to cezenai so it's reachable as cezenai.local via mDNS + - echo "cezenai" > /target/etc/hostname + - sed -i 's/aiserver/cezenai/g' /target/etc/hosts || true + + # Create cezen-setup web UI systemd service - | cat > /target/etc/systemd/system/cezen-setup.service << 'EOF' [Unit] - Description=Cezen AI Suite Setup Wizard - After=network-online.target getty@tty1.service + Description=Cezen AI Suite — Web Setup UI + After=network-online.target avahi-daemon.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 + Type=simple + ExecStart=/usr/bin/python3 /opt/cezen/websetup.py + Restart=on-failure + RestartSec=5 [Install] WantedBy=multi-user.target EOF - curtin in-target -- systemctl enable cezen-setup.service + - curtin in-target -- systemctl enable avahi-daemon.service # ── Skip confirmations ───────────────────────── user-data: diff --git a/autoinstall/websetup/server.py b/autoinstall/websetup/server.py new file mode 100644 index 0000000..c038000 --- /dev/null +++ b/autoinstall/websetup/server.py @@ -0,0 +1,666 @@ +#!/usr/bin/env python3 +""" +Cezen AI Suite — First Boot Web Setup Server +Serves on port 80. Access from any browser on the same network. +""" +import os, json, subprocess, threading, time, socket, ipaddress +from http.server import HTTPServer, BaseHTTPRequestHandler +from urllib.parse import parse_qs, urlparse + +SETUP_DONE_FILE = "/opt/cezen/.setup-done" +INSTALL_LOG = "/var/log/cezen-install.log" +AIPACKAGE_DIR = "/opt/aipackage" +install_proc = None +install_status = {"running": False, "done": False, "error": None} + +# ─── Helpers ────────────────────────────────────────────── +def get_ip(): + try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + ip = s.getsockname()[0] + s.close() + return ip + except: + return "unknown" + +def get_interfaces(): + try: + out = subprocess.check_output(["ip", "-o", "link", "show"], text=True) + ifaces = [] + for line in out.splitlines(): + name = line.split(": ")[1].split("@")[0] + if name not in ("lo",) and not name.startswith(("docker","br-","veth","k3s")): + ifaces.append(name) + return ifaces + except: + return ["eth0"] + +def apply_static_ip(iface, ip, prefix, gateway, dns): + config = f"""network: + version: 2 + ethernets: + {iface}: + dhcp4: false + addresses: + - {ip}/{prefix} + routes: + - to: default + via: {gateway} + nameservers: + addresses: [{dns}] +""" + with open("/etc/netplan/99-cezen-static.yaml", "w") as f: + f.write(config) + subprocess.run(["netplan", "apply"], capture_output=True) + time.sleep(3) + +def run_install(tier, skip_tools): + global install_status + install_status = {"running": True, "done": False, "error": None} + try: + # Write config so phase 2 (post-reboot) knows what to skip + os.makedirs("/opt/cezen", exist_ok=True) + skip_str = ",".join(skip_tools) if skip_tools else "" + with open("/opt/cezen/install.conf", "w") as f: + f.write(f"TIER={tier}\nSKIP_ROLES={skip_str}\n") + + # Mark setup done NOW so this web UI doesn't restart after the phase-1 reboot + 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}"] + with open(INSTALL_LOG, "w") as log: + proc = subprocess.Popen(cmd, stdout=log, stderr=log, env=env) + proc.wait() + # Reaches here only if no reboot happened (e.g. no GPU / drivers already installed) + install_status = {"running": False, "done": True, "error": None} + except Exception as e: + install_status = {"running": False, "done": False, "error": str(e)} + +# ─── HTML UI ────────────────────────────────────────────── +HTML = r""" + +
+ + +Choose how this server gets its IP address. You can change this later.
+ +Choose the tier that matches your GPU hardware.
+Toggle the components you want installed. Recommended defaults are pre-selected.
+Confirm your settings before installation begins.
+ + +Starting...
+ +Your Cezen AI Suite is ready.
+