Files
NexusOS/install-windows.ps1
T
jonandClaude Opus 4.8 ca8bc7425c Sync from upstream: ncp is Python now, runs on Windows too
management/ncp.py replaces the bash CLI's logic; nexus-cli.sh and the new
ncp.ps1 are thin wrappers, so Linux keeps its entry point and Windows gains one.
psutil handles process and port work on both platforms.

install-windows.ps1 registers ncp in the PowerShell profile. The panel VPN
switch now resolves its WireGuard connection through NetworkManager instead of
a hardcoded name, and the .ps1 ASCII guard globs rather than naming files.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 11:34:15 -05:00

253 lines
12 KiB
PowerShell

#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.
.PARAMETER InvokerProfile
Internal. The PowerShell profile path of the user who launched the
installer, captured before self-elevation and passed to the elevated run.
If a standard account elevates with a DIFFERENT admin's credentials, the
elevated process sees that admin's $PROFILE - so ncp would be registered
for the wrong user. Not meant to be passed by hand.
#>
param(
[string]$InvokerProfile = ""
)
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
# Capture THIS user's profile path before elevating and hand it to the
# elevated run: if UAC prompts for another admin's credentials, that process
# would otherwise register ncp in the wrong user's profile.
$Invoker = $PROFILE.CurrentUserAllHosts
Start-Process powershell -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`" -InvokerProfile `"$Invoker`"" -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."
}
# -- ncp command ---------------------------------------------------------------
# The Linux side registers ncp in ~/.bashrc from restore-linux.sh; this is the
# same idea for PowerShell. Guarded by a marker so re-running the installer is a
# no-op instead of stacking duplicate functions.
Write-Step "Registering the ncp command"
try {
# CurrentUserAllHosts (profile.ps1), not $PROFILE: $PROFILE is host-specific,
# so ncp would exist in the console but not in the VS Code terminal or ISE.
# Prefer the invoking user's path when we were elevated - see -InvokerProfile.
$ProfilePath = if ($InvokerProfile) { $InvokerProfile } else { $PROFILE.CurrentUserAllHosts }
$ProfileDir = Split-Path -Parent $ProfilePath
if (-not (Test-Path $ProfileDir)) {
New-Item -ItemType Directory -Path $ProfileDir -Force | Out-Null
}
$Marker = "# NexusOS ncp"
$Existing = ""
if (Test-Path $ProfilePath) {
$Existing = Get-Content -Path $ProfilePath -Raw -ErrorAction SilentlyContinue
}
if ($Existing -and $Existing.Contains($Marker)) {
Write-OK "ncp already registered in $ProfilePath"
} else {
$NcpPs1 = Join-Path $RepoRoot "management\ncp.ps1"
# Without this, a Join-Path that fails writes `function ncp { & "" }` -
# a broken profile with no error. Fail into the catch below instead.
if (-not (Test-Path $NcpPs1)) { throw "ncp.ps1 not found at '$NcpPs1'" }
# ASCII encoding: the content is ASCII anyway, and -Encoding UTF8 on
# PowerShell 5.1 can plant a BOM in the middle of an existing profile.
Add-Content -Path $ProfilePath -Encoding ASCII -Value ""
Add-Content -Path $ProfilePath -Encoding ASCII -Value $Marker
Add-Content -Path $ProfilePath -Encoding ASCII -Value "function ncp { & `"$NcpPs1`" @args }"
Write-OK "ncp registered in $ProfilePath"
}
} catch {
Write-Warn "Could not register ncp - add this line to your PowerShell profile:"
Write-Warn " function ncp { & '$RepoRoot\management\ncp.ps1' @args }"
}
# -- 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 ""
Write-Host " Terminal: open a NEW PowerShell window, then run ncp help" -ForegroundColor White
Write-Host " (the profile that defines ncp is only read at startup)" -ForegroundColor DarkGray
Write-Host " ==========================================================" -ForegroundColor Green
Write-Host ""
Read-Host "Press Enter to close"