Files
NexusOS/install-windows.ps1
T
jonandClaude Opus 4.8 4961d765c8 fix(install-windows): pull models last, so Ctrl+C can't abort the install
Ctrl+C in PS 5.1 kills the whole script, and the multi-GB model pull sat in the
middle -- skipping the download also skipped the ncp PATH registration and the
desktop shortcut. Moves the pull after them. Also drops the pipe that buffered
ollama's progress bar (a running download looked like a hang) and replaces a
try/catch that native commands never trigger with $LASTEXITCODE checks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 14:30:09 -05:00

247 lines
12 KiB
PowerShell

#Requires -Version 5.1
<#
.SYNOPSIS
NexusOS installer for Windows (native).
.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 reboot needed.
#>
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-windows.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)"
# -- ncp command ---------------------------------------------------------------
# management\ncp.cmd goes on the machine PATH rather than a `function ncp` in the
# PowerShell profile. Three reasons the profile route kept biting:
# 1. Execution policy defaults to Restricted, which blocks the profile itself -
# so the function was never defined, and ncp.ps1 could not have run anyway.
# 2. Profiles are a PowerShell thing. cmd, Win+R, Task Scheduler and .lnk
# targets all reported "ncp is not recognized".
# 3. We are elevated here, so $PROFILE is the ADMIN's profile - the whole
# -InvokerProfile dance existed to work around that. Machine PATH has no
# such split.
# The Linux side does the matching thing: a symlink in /usr/local/bin.
Write-Step "Registering the ncp command"
try {
$NcpCmd = Join-Path $RepoRoot "management\ncp.cmd"
if (-not (Test-Path $NcpCmd)) { throw "ncp.cmd not found at '$NcpCmd'" }
$BinDir = Split-Path -Parent $NcpCmd
# Machine scope needs admin, which the self-elevation above already gave us.
# Read-modify-write through .NET, NEVER setx: setx silently truncates PATH at
# 1024 characters and has permanently broken machines.
$Current = [Environment]::GetEnvironmentVariable("Path", "Machine")
$Entries = @($Current -split ';' | Where-Object { $_ -ne "" })
if ($Entries -contains $BinDir) {
Write-OK "ncp already on the machine PATH ($BinDir)"
} else {
$New = (@($Entries) + $BinDir) -join ';'
[Environment]::SetEnvironmentVariable("Path", $New, "Machine")
Write-OK "ncp registered on the machine PATH ($BinDir)"
}
# PATH is read at process start, so this shell and any already-open one still
# do not have it. Patch ours so the rest of the installer can call ncp.
$env:Path = "$env:Path;$BinDir"
} catch {
Write-Warn "Could not put ncp on PATH - add this directory to PATH by hand:"
Write-Warn " $RepoRoot\management"
}
# -- 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"
}
# -- Pull models (best-effort, LAST on purpose) --------------------------------
# This is the only multi-GB step, and the only one a user is invited to Ctrl+C.
# Ctrl+C in PowerShell 5.1 terminates the whole SCRIPT, not just the running
# native command - so with this block in the middle, skipping the download also
# skipped the PATH registration and the desktop shortcut, and left an install
# that looked finished but had no working `ncp`. Everything that makes NexusOS
# usable now runs before we get here; abort at this point and you lose only the
# models, which the Models tab can pull later.
#
# 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
# No pipe: 'ollama pull' draws a progress bar with cursor control, and piping it
# (to Out-Host or anything else) buffers the redraws - the download then shows no
# output for minutes and reads as a hang. Let it own the console.
# No try/catch either: a native command that exits non-zero does not throw, so
# the catch never fired and a failed pull was reported as success.
ollama pull $ChatModel
if ($LASTEXITCODE -eq 0) { Write-OK "$ChatModel ready (default chat model)" }
else { Write-Warn "$ChatModel pull skipped/failed - pull it from the Models tab later." }
ollama pull $MemModel
if ($LASTEXITCODE -eq 0) { Write-OK "$MemModel ready (memory curator)" }
else { 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."
}
# -- 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 ""
Write-Host " Terminal: open a NEW terminal (PowerShell or cmd), then run ncp help" -ForegroundColor White
Write-Host " (PATH is read at process start, so open windows lack it)" -ForegroundColor DarkGray
Write-Host " ==========================================================" -ForegroundColor Green
Write-Host ""
Read-Host "Press Enter to close"