#!/usr/bin/env bash # NexusOS WSL bootstrap — called by install-windows.ps1 after the repo is copied in. # Safe to re-run; skips steps that are already done. set -euo pipefail NEXUS_ROOT="$(cd "$(dirname "$0")/.." && pwd)" cd "$NEXUS_ROOT" step() { echo ""; echo "==> $*"; } ok() { echo " ok: $*"; } warn() { echo " warn: $*"; } # ── System packages ─────────────────────────────────────────────────────────── step "System packages" sudo apt-get update -qq sudo apt-get install -y -qq \ python3 python3-venv python3-pip python3-tk \ curl wget git build-essential \ libsqlite3-dev rsync ok "System packages ready" # ── Node.js (via NodeSource LTS) ────────────────────────────────────────────── step "Node.js" if command -v node &>/dev/null; then ok "Already installed: $(node --version)" else curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - 2>/dev/null sudo apt-get install -y -qq nodejs ok "Installed: $(node --version)" fi # ── Python venv ─────────────────────────────────────────────────────────────── step "Python venv (Promethean)" if [ ! -d "$NEXUS_ROOT/Promethean" ]; then python3 -m venv "$NEXUS_ROOT/Promethean" ok "Created" else ok "Already exists" fi source "$NEXUS_ROOT/Promethean/bin/activate" pip install --upgrade pip -q step "Python dependencies" pip install -r "$NEXUS_ROOT/requirements-wsl.txt" -q ok "Installed" # ── Ollama ──────────────────────────────────────────────────────────────────── step "Ollama" mkdir -p "$NEXUS_ROOT/runtime/logs" OLLAMA_BIN="$NEXUS_ROOT/ollama/bin/ollama" DEFAULT_MODEL="${NEXUS_DEFAULT_MODEL:-qwen2.5:3b}" case "$(uname -m)" in x86_64|amd64) ;; *) echo "ERROR: bundled Ollama supports x86-64 WSL only (detected $(uname -m))." >&2 exit 1 ;; esac if [ ! -x "$OLLAMA_BIN" ]; then chmod +x "$OLLAMA_BIN" 2>/dev/null || true fi if [ ! -x "$OLLAMA_BIN" ] || ! "$OLLAMA_BIN" --version &>/dev/null; then echo "ERROR: bundled Ollama could not run in this WSL distro." >&2 echo " Expected a Linux x86-64 executable at: $OLLAMA_BIN" >&2 exit 1 fi ok "Binary OK: $("$OLLAMA_BIN" --version)" # A clean checkout has no model files. Provision one so the first launch can chat. OLLAMA_HOST="127.0.0.1:11434" OLLAMA_MODELS="$NEXUS_ROOT/models" OLLAMA_HOST="$OLLAMA_HOST" OLLAMA_MODELS="$OLLAMA_MODELS" \ "$OLLAMA_BIN" list 2>/dev/null | awk -v model="$DEFAULT_MODEL" \ 'NR > 1 && $1 == model { found = 1 } END { exit !found }' \ || { ollama_pid="" if ! curl -fsS "http://127.0.0.1:11434/api/tags" >/dev/null 2>&1; then OLLAMA_HOST="$OLLAMA_HOST" OLLAMA_MODELS="$NEXUS_ROOT/models" \ "$OLLAMA_BIN" serve >>"$NEXUS_ROOT/runtime/logs/ollama.log" 2>&1 & ollama_pid=$! for _ in {1..30}; do curl -fsS "http://127.0.0.1:11434/api/tags" >/dev/null 2>&1 && break sleep 1 done fi if ! curl -fsS "http://127.0.0.1:11434/api/tags" >/dev/null 2>&1; then [ -n "$ollama_pid" ] && kill "$ollama_pid" 2>/dev/null || true echo "ERROR: Ollama did not start; see runtime/logs/ollama.log" >&2 exit 1 fi if ! OLLAMA_HOST="$OLLAMA_HOST" OLLAMA_MODELS="$OLLAMA_MODELS" \ "$OLLAMA_BIN" list 2>/dev/null | awk -v model="$DEFAULT_MODEL" \ 'NR > 1 && $1 == model { found = 1 } END { exit !found }'; then echo " pulling default model: $DEFAULT_MODEL" if ! OLLAMA_HOST="$OLLAMA_HOST" OLLAMA_MODELS="$OLLAMA_MODELS" \ "$OLLAMA_BIN" pull "$DEFAULT_MODEL"; then [ -n "$ollama_pid" ] && kill "$ollama_pid" 2>/dev/null || true echo "ERROR: Could not pull default model $DEFAULT_MODEL." >&2 exit 1 fi fi [ -n "$ollama_pid" ] && kill "$ollama_pid" 2>/dev/null || true ok "Model ready: $DEFAULT_MODEL" } # ── Frontend npm deps ───────────────────────────────────────────────────────── step "Frontend dependencies" cd "$NEXUS_ROOT/interface/web" npm install --silent ok "Installed" cd "$NEXUS_ROOT" # ── Runtime dirs ───────────────────────────────────────────────────────────── step "Runtime directories" mkdir -p "$NEXUS_ROOT/runtime/pids" "$NEXUS_ROOT/runtime/logs" ok "Ready" # ── Shell alias (ncp) ───────────────────────────────────────────────────────── step "Shell alias" if ! grep -q "nexus-core/management/nexus-cli.sh" "$HOME/.bashrc" 2>/dev/null; then cat >> "$HOME/.bashrc" << 'EOF' # NexusOS ncp() { ~/nexus-core/management/nexus-cli.sh "$@"; } EOF ok "ncp alias added to ~/.bashrc" else ok "ncp already in ~/.bashrc" fi step "Promethean command" PROMETHEAN_RC="$NEXUS_ROOT/.promethean_bashrc" if [ ! -f "$PROMETHEAN_RC" ]; then cat > "$PROMETHEAN_RC" << 'EOF' # Promethean Terminal shell init [[ -f ~/.bashrc ]] && source ~/.bashrc export VIRTUAL_ENV_DISABLE_PROMPT=1 if [[ -f "$HOME/nexus-core/Promethean/bin/activate" ]]; then source "$HOME/nexus-core/Promethean/bin/activate" fi export PS1='\[\e[0;35m\](Promethean)\[\e[0m\] ${debian_chroot:+($debian_chroot)}\[\e[0;37m\]\u@\h\[\e[0m\]:\[\e[1;32m\]\w\[\e[0m\]\$ ' printf '\e]0;Promethean Terminal\a' EOF ok "Promethean environment init created" else ok "Promethean environment init already exists" fi if ! grep -qF "alias promethean='source ~/nexus-core/.promethean_bashrc'" "$HOME/.bashrc" 2>/dev/null; then cat >> "$HOME/.bashrc" << 'EOF' # Promethean alias promethean='source ~/nexus-core/.promethean_bashrc' EOF ok "promethean command added to ~/.bashrc" else ok "promethean command already in ~/.bashrc" fi # ── Windows Terminal profile + desktop shortcut ─────────────────────────────── setup_windows() { local win_user distro icon_src icon_win_dir icon_win_path win_user=$(powershell.exe -command '$env:USERNAME' 2>/dev/null | tr -d '\r\n') distro="${WSL_DISTRO_NAME:-$(uname -n)}" if [ -z "$win_user" ] || [ ! -d "/mnt/c/Users/$win_user" ]; then warn "Cannot locate Windows user directory — skipping Windows Terminal setup" return 0 fi # Copy app icon into Windows filesystem so WT can display it icon_src="$NEXUS_ROOT/assets/n-small.png" icon_win_dir="/mnt/c/Users/$win_user/AppData/Local/NexusOS" mkdir -p "$icon_win_dir" [ -f "$icon_src" ] && cp "$icon_src" "$icon_win_dir/nexus.png" icon_win_path="C:\\Users\\$win_user\\AppData\\Local\\NexusOS\\nexus.png" # Init file so the WT session sources nvm + ncp cat > "$HOME/.nexus_init" << 'EOF' [[ -f ~/.bashrc ]] && source ~/.bashrc EOF # Patch Windows Terminal settings.json via Python (handles JSONC safely) python3 - "$win_user" "$distro" "$icon_win_path" << 'PYEOF' import json, re, uuid, sys from pathlib import Path win_user, distro, icon_path = sys.argv[1], sys.argv[2], sys.argv[3] candidates = [ f"/mnt/c/Users/{win_user}/AppData/Local/Packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState/settings.json", f"/mnt/c/Users/{win_user}/AppData/Local/Packages/Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe/LocalState/settings.json", ] settings_path = next((Path(p) for p in candidates if Path(p).exists()), None) if not settings_path: print(" warn: Windows Terminal not found — skipping profile") sys.exit(0) def strip_jsonc(text): output = [] in_string = False escaped = False in_line_comment = False in_block_comment = False index = 0 while index < len(text): char = text[index] next_char = text[index + 1] if index + 1 < len(text) else "" if in_line_comment: if char == "\n": in_line_comment = False output.append(char) elif in_block_comment: if char == "*" and next_char == "/": in_block_comment = False index += 1 elif in_string: output.append(char) if escaped: escaped = False elif char == "\\": escaped = True elif char == '"': in_string = False elif char == '"': in_string = True output.append(char) elif char == "/" and next_char == "/": in_line_comment = True index += 1 elif char == "/" and next_char == "*": in_block_comment = True index += 1 else: output.append(char) index += 1 return "".join(output) try: with open(settings_path, encoding="utf-8") as f: settings_text = strip_jsonc(f.read()) settings = json.loads(re.sub(r",(\s*[}\]])", r"\1", settings_text)) except (OSError, json.JSONDecodeError) as error: print(f" warn: Could not read Windows Terminal settings: {error}") sys.exit(0) profiles = settings.setdefault("profiles", {}).setdefault("list", []) if any(p.get("name") == "NexusOS" for p in profiles): print(" ok: NexusOS profile already in Windows Terminal") sys.exit(0) profiles.insert(0, { "guid": "{" + str(uuid.uuid4()) + "}", "name": "NexusOS", "commandline": f"wsl.exe -d {distro} bash --init-file ~/.nexus_init", "startingDirectory": "~", "icon": icon_path, }) try: with open(settings_path, "w", encoding="utf-8") as f: json.dump(settings, f, indent=4) except OSError as error: print(f" warn: Could not update Windows Terminal settings: {error}") sys.exit(0) print(f" ok: NexusOS profile added to Windows Terminal") PYEOF # Desktop shortcut — opens WT to the NexusOS profile and launches Nexus powershell.exe -command " \$ws = New-Object -ComObject WScript.Shell \$s = \$ws.CreateShortcut(\"\$env:USERPROFILE\Desktop\NexusOS.lnk\") \$s.TargetPath = 'wt.exe' \$s.Arguments = '--profile NexusOS -- bash -c \"cd ~/nexus-core && ./launch_nexus.sh ; exec bash\"' \$s.IconLocation = 'wt.exe,0' \$s.Save() " 2>/dev/null && ok "Desktop shortcut created" || warn "Could not create desktop shortcut" } step "Windows integration" if grep -qi microsoft /proc/version 2>/dev/null; then setup_windows else ok "Not running in WSL — skipping Windows Terminal setup" fi # ── Done ────────────────────────────────────────────────────────────────────── echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo " NexusOS bootstrap complete!" echo "" echo " Launch: cd ~/nexus-core && ./launch_nexus.sh" echo " Frontend: http://localhost:5173" echo " Backend: http://localhost:8000" echo "" echo " Or use the NexusOS shortcut on your Windows desktop." echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"