#Requires -Version 5.1 <# .SYNOPSIS NexusOS installer for Windows (native - no WSL). .DESCRIPTION Installs Python, Node.js, and Ollama via winget, builds the Promethean virtualenv and the web UI, and drops a desktop shortcut. NexusOS then runs as a single process: the backend on :8000 serves the built UI, and the AI (Ollama) is started manually from the UI. Run from the nexus-core directory: Right-click install-windows.ps1 -> "Run with PowerShell" Requires Windows 10/11 with winget (App Installer). No WSL, no reboot. #> Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" $RepoRoot = $PSScriptRoot # -- Helpers ------------------------------------------------------------------- function Write-Step { param([string]$Msg) Write-Host "`n==> $Msg" -ForegroundColor Cyan } function Write-OK { param([string]$Msg) Write-Host " ok: $Msg" -ForegroundColor Green } function Write-Warn { param([string]$Msg) Write-Host " warn: $Msg" -ForegroundColor Yellow } function Write-Fail { param([string]$Msg) Write-Host "`n ERROR: $Msg`n" -ForegroundColor Red; Read-Host "Press Enter to exit"; exit 1 } # Pull the current machine + user PATH out of the registry into this session, so # tools winget just installed become runnable without opening a new shell. function Update-SessionPath { $m = [System.Environment]::GetEnvironmentVariable("Path", "Machine") $u = [System.Environment]::GetEnvironmentVariable("Path", "User") $env:Path = ($m, $u | Where-Object { $_ }) -join ";" } function Install-Winget { param([string]$Id, [string]$Label) Write-Step "Installing $Label" winget install --id $Id -e --source winget ` --accept-package-agreements --accept-source-agreements --disable-interactivity | Out-Host # winget returns non-zero when the package is already installed / up to date; # that's not a failure for us. Verify presence after refreshing PATH instead. Update-SessionPath Write-OK "$Label step done" } # -- Self-elevate to Administrator --------------------------------------------- $IsAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole( [Security.Principal.WindowsBuiltInRole]::Administrator) if (-not $IsAdmin) { Write-Host "Requesting administrator privileges..." -ForegroundColor Yellow Start-Process powershell -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs exit } Write-Host "" Write-Host " NexusOS Installer for Windows (native)" -ForegroundColor White Write-Host " ======================================" -ForegroundColor DarkGray # -- winget present? ----------------------------------------------------------- Write-Step "Checking winget" if (-not (Get-Command winget -ErrorAction SilentlyContinue)) { Write-Fail "winget not found. Install 'App Installer' from the Microsoft Store, then re-run this installer." } Write-OK "winget available" # -- Install runtimes ---------------------------------------------------------- Install-Winget "Python.Python.3.12" "Python 3.12" Install-Winget "OpenJS.NodeJS.LTS" "Node.js LTS" Install-Winget "Ollama.Ollama" "Ollama" # -- Verify tools -------------------------------------------------------------- Write-Step "Verifying tools" foreach ($t in @("python", "npm", "ollama")) { if (-not (Get-Command $t -ErrorAction SilentlyContinue)) { Write-Fail "$t is not on PATH after install. Close this window, open a new PowerShell, and re-run the installer." } Write-OK "$t found" } # -- Python venv + deps -------------------------------------------------------- Write-Step "Building the Promethean virtualenv" $Venv = Join-Path $RepoRoot "Promethean" $VenvPy = Join-Path $Venv "Scripts\python.exe" if (-not (Test-Path $VenvPy)) { python -m venv $Venv } if (-not (Test-Path $VenvPy)) { Write-Fail "venv creation failed at $Venv" } & $VenvPy -m pip install --upgrade pip -q $Req = Join-Path $RepoRoot "requirements-wsl.txt" # CPU / pure-Python overlay - right for native Windows too if (-not (Test-Path $Req)) { Write-Fail "requirements file not found: $Req" } & $VenvPy -m pip install -r $Req if ($LASTEXITCODE -ne 0) { Write-Fail "pip install failed (exit $LASTEXITCODE) - see output above" } Write-OK "Python environment ready" # -- Frontend build ------------------------------------------------------------ Write-Step "Building the web UI" Push-Location (Join-Path $RepoRoot "interface\web") npm install if ($LASTEXITCODE -ne 0) { Pop-Location; Write-Fail "npm install failed (exit $LASTEXITCODE) - see output above" } npm run build if ($LASTEXITCODE -ne 0) { Pop-Location; Write-Fail "npm run build failed (exit $LASTEXITCODE) - no UI would be served" } Pop-Location if (-not (Test-Path (Join-Path $RepoRoot "interface\web\dist\index.html"))) { Write-Fail "build reported success but interface\web\dist\index.html is missing" } Write-OK "Web UI built (interface\web\dist)" # -- Pull models (best-effort) ------------------------------------------------- # Which models ship is decided in ONE place - DEFAULT_CHAT_MODEL and # DEFAULT_MEMORY_MODEL in synapse\nexus_config.py (rationale documented there). # Read them instead of hardcoding, so the installer can never pull one model # while the backend defaults to another. Push-Location $RepoRoot $ChatModel = (& $VenvPy -c "from synapse.nexus_config import DEFAULT_CHAT_MODEL as m; print(m)") $MemModel = (& $VenvPy -c "from synapse.nexus_config import DEFAULT_MEMORY_MODEL as m; print(m)") Pop-Location if ($LASTEXITCODE -ne 0 -or -not $ChatModel -or -not $MemModel) { Write-Fail "Could not read the default models from synapse\nexus_config.py - the venv install is broken" } Write-Step "Pulling models ($ChatModel for chat, $MemModel for memory)" Write-Host " Downloads a few GB; press Ctrl+C to skip and pull them later from the Models tab." -ForegroundColor DarkGray try { ollama pull $ChatModel | Out-Host Write-OK "$ChatModel ready (default chat model)" } catch { Write-Warn "$ChatModel pull skipped/failed - pull it from the Models tab later." } try { ollama pull $MemModel | Out-Host Write-OK "$MemModel ready (memory curator)" } catch { Write-Warn "$MemModel pull skipped/failed - the memory service will fall back to the chat model." } # Pin it as the default chat model. Runs from the repo root so the synapse # package imports; only writes the 'model' setting in the shared DB. Write-Step "Setting $ChatModel as the default model" Push-Location $RepoRoot & $VenvPy -c "from synapse.memory.store import store; from synapse.nexus_config import DEFAULT_CHAT_MODEL; store.update_settings({'model': DEFAULT_CHAT_MODEL})" $seedOk = ($LASTEXITCODE -eq 0) Pop-Location if ($seedOk) { Write-OK "Default model set to $ChatModel" } else { Write-Warn "Could not persist default model - pick it at the top of the chat instead." } # -- Make Ollama manual-start (NexusOS owns the lifecycle) ---------------------- Write-Step "Setting Ollama to manual start" # The Ollama desktop app autostarts a server at every login, and the elevated # 'ollama pull' above leaves an elevated server the user-level app cannot stop - # which makes the Start/Stop AI button get stuck. Remove the login autostart and # stop the running server so NexusOS controls Ollama via its Start AI button. try { $ollamaAutostart = Join-Path ([Environment]::GetFolderPath("Startup")) "Ollama.lnk" if (Test-Path $ollamaAutostart) { Remove-Item $ollamaAutostart -Force Write-OK "Removed Ollama login autostart" } foreach ($img in @("ollama app.exe", "ollama.exe")) { taskkill /F /T /IM $img 2>$null | Out-Null } Write-OK "Ollama set to manual start" } catch { Write-Warn "Could not adjust Ollama autostart - you can still Start/Stop AI from the app." } # -- Desktop shortcut ---------------------------------------------------------- Write-Step "Creating desktop shortcut" try { $Launcher = Join-Path $RepoRoot "launch_nexus.ps1" $LnkPath = Join-Path ([Environment]::GetFolderPath("Desktop")) "NexusOS.lnk" $ws = New-Object -ComObject WScript.Shell $lnk = $ws.CreateShortcut($LnkPath) $lnk.TargetPath = "powershell.exe" $lnk.Arguments = "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$Launcher`"" $lnk.WorkingDirectory = $RepoRoot $lnk.Description = "Launch NexusOS" $Ico = Join-Path $RepoRoot "assets\NexusOS.ico" if (Test-Path $Ico) { $lnk.IconLocation = "$Ico,0" } $lnk.Save() Write-OK "Desktop shortcut created" } catch { Write-Warn "Could not create desktop shortcut - launch with: powershell -File launch_nexus.ps1" } # -- Done ---------------------------------------------------------------------- Write-Host "" Write-Host " ==========================================================" -ForegroundColor Green Write-Host " NexusOS is installed!" -ForegroundColor Green Write-Host "" Write-Host " Launch: double-click the NexusOS shortcut on your desktop" -ForegroundColor White Write-Host " (or run powershell -File launch_nexus.ps1)" -ForegroundColor White Write-Host "" Write-Host " The app opens at http://localhost:8000" -ForegroundColor White Write-Host " The AI starts OFF - click 'Start AI' in the sidebar to turn it on." -ForegroundColor White Write-Host " ==========================================================" -ForegroundColor Green Write-Host "" Read-Host "Press Enter to close"