Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions docs/get-started/windows-preparation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@ $ Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/NVIDIA/NemoClaw/main
The command downloads the script to a temporary file before running it.
`-ExecutionPolicy Bypass` applies only to that PowerShell process and avoids local policy blocking the downloaded script.
Run it from Windows, not from inside WSL.
The script requests Administrator privileges when needed, enables the required WSL 2 Windows features, installs or opens Ubuntu, and installs and starts Docker Desktop.
If Ubuntu is already registered, the script confirms it uses WSL 2, converts it from WSL 1 when needed, and verifies Docker is reachable from WSL.
The script requests Administrator privileges when needed, enables the required WSL 2 Windows features, installs or opens Ubuntu 24.04, and installs and starts Docker Desktop.
If the target Ubuntu distro is already registered, the script confirms it uses WSL 2, converts it from WSL 1 when needed, and verifies Docker is reachable from WSL.
If Windows requires a reboot after enabling WSL features, the script prompts for the reboot and registers a one-time continuation for the next sign-in.
If Docker Desktop shows first-run prompts, complete them and return to the PowerShell window.

For advanced options, download the script first and run `Get-Help "$env:TEMP\bootstrap-windows.ps1" -Detailed`.
Useful parameters include `-DistroName`, `-InstallerUrl`, `-InstallerArgs`, and `-InstallDockerDesktop`.
The default distro is `Ubuntu-24.04`.
To reuse an existing distro named `Ubuntu`, pass `-DistroName Ubuntu`.

The bootstrap script does not install NemoClaw itself.
When Windows preparation is complete, it opens Ubuntu and prints the standard installer command to run inside Ubuntu:
Expand Down Expand Up @@ -74,15 +76,15 @@ Reboot if prompted.
After reboot, open an elevated PowerShell again:

```console
$ wsl --install -d Ubuntu
$ wsl --install -d Ubuntu-24.04
```

Let the distribution launch and complete first-run setup (pick a Unix username and password), then type `exit` to return to PowerShell.

<Warning>
Do not use the `--no-launch` flag.
The `--no-launch` flag downloads the package but does not register the distribution with WSL.
Commands like `wsl -d Ubuntu` fail with "There is no distribution with the supplied name" until the distribution has been launched at least once.
Commands like `wsl -d Ubuntu-24.04` fail with "There is no distribution with the supplied name" until the distribution has been launched at least once.
</Warning>

Verify the distribution is registered and running WSL 2:
Expand All @@ -94,8 +96,8 @@ $ wsl -l -v
Expected output:

```text
NAME STATE VERSION
* Ubuntu Running 2
NAME STATE VERSION
* Ubuntu-24.04 Running 2
```

## Install Docker Desktop
Expand Down
80 changes: 27 additions & 53 deletions scripts/bootstrap-windows.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ $script:DockerDesktopExe = 'C:\Program Files\Docker\Docker\Docker Desktop.exe'
$script:DockerCli = 'C:\Program Files\Docker\Docker\resources\bin\docker.exe'
$script:WingetDockerId = 'Docker.DockerDesktop'
$script:InstallerWindowTitle = "NVIDIA NemoClaw Installer ($PID)"
$script:InstallDistroAtHandoff = $false

function Write-Status {
param(
Expand Down Expand Up @@ -655,7 +656,7 @@ function Ensure-WslDistroVersion2 {
function Get-WslInstallCommandText {
param([Parameter(Mandatory)] [string]$Name)

return "wsl --install $Name"
return "wsl --install -d $Name"
}

function Write-WslUbuntuRequiredNotice {
Expand All @@ -664,7 +665,7 @@ function Write-WslUbuntuRequiredNotice {
Write-Host ''
if ($Name -eq 'Ubuntu-24.04') {
Write-Host 'NemoClaw on Windows ARM requires WSL2 Ubuntu 24.04.' -ForegroundColor Yellow
Write-Host 'Please run: wsl --install Ubuntu-24.04' -ForegroundColor Yellow
Write-Host "Please run: $(Get-WslInstallCommandText -Name $Name)" -ForegroundColor Yellow
Write-Host 'Then re-run this installer.' -ForegroundColor Yellow
} else {
Write-Host "NemoClaw on Windows requires a WSL2 distro named $Name." -ForegroundColor Yellow
Expand All @@ -674,62 +675,18 @@ function Write-WslUbuntuRequiredNotice {
Write-Host ''
}

function Wait-WslDistroRegistration {
param(
[Parameter(Mandatory)] [string]$Name,
[int]$TimeoutSeconds = 300
)

$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
while ((Get-Date) -lt $deadline) {
$distros = Get-WslDistros
if ($distros -contains $Name) {
return $true
}
Start-Sleep -Seconds 5
}
return $false
}

function Install-WslDistro {
param([Parameter(Mandatory)] [string]$Name)

$wsl = Resolve-WslExe
$displayCommand = Get-WslInstallCommandText -Name $Name
Write-Status "$Name is not registered. Installing with: $displayCommand"
$installExitCode = Invoke-NativeCommand -FilePath $wsl -ArgumentList @('--install', $Name)
if ($installExitCode -ne 0) {
Write-Status -Level WARN "$displayCommand failed with exit code $installExitCode."
Write-WslUbuntuRequiredNotice -Name $Name
throw "Could not install WSL distro '$Name'."
}

if (-not (Wait-WslDistroRegistration -Name $Name)) {
Write-WslUbuntuRequiredNotice -Name $Name
throw "WSL distro '$Name' was not registered after running $displayCommand."
}
}

function Ensure-UbuntuWsl {
$wsl = Resolve-WslExe
$script:InstallDistroAtHandoff = $false

$installedDistro = $false
$distros = Get-WslDistros
if ($distros -notcontains $DistroName) {
Install-WslDistro -Name $DistroName
$installedDistro = $true
$distros = Get-WslDistros
if ($distros -notcontains $DistroName) {
Write-WslUbuntuRequiredNotice -Name $DistroName
throw "WSL distro '$DistroName' is still not registered after install."
}
Write-Status "$DistroName is not registered yet. It will be installed during the final Ubuntu handoff."
$script:InstallDistroAtHandoff = $true
return
}

if ($installedDistro) {
Write-Status "WSL distro registered: $DistroName"
} else {
Write-Status "WSL distro already registered: $DistroName"
}
Write-Status "WSL distro already registered: $DistroName"

Ensure-WslDistroVersion2 -Name $DistroName

Expand Down Expand Up @@ -846,6 +803,11 @@ function Get-NemoClawInstallerCommand {
function Open-UbuntuForInstaller {
$wsl = Resolve-WslExe
try {
if ($script:InstallDistroAtHandoff) {
Start-Process -FilePath $wsl -ArgumentList @('--install', '-d', $DistroName) | Out-Null
return
}

$ubuntuLauncher = Resolve-UbuntuLauncher
$windowsTerminal = Get-Command 'wt.exe' -ErrorAction SilentlyContinue
if ($windowsTerminal) {
Expand All @@ -863,6 +825,10 @@ function Open-UbuntuForInstaller {
Start-Process -FilePath $wsl -ArgumentList @('-d', $DistroName) | Out-Null
} catch {
Write-Status -Level WARN "Could not open $DistroName automatically: $($_.Exception.Message)"
if ($script:InstallDistroAtHandoff) {
Write-WslUbuntuRequiredNotice -Name $DistroName
throw
}
}
}

Expand All @@ -871,7 +837,11 @@ function Write-InstallerHandoff {
Write-Host ''
Write-Host 'Windows preparation is complete.' -ForegroundColor Green
Write-Host ''
Write-Host "An Ubuntu window is opening. Run this command inside Ubuntu to install NemoClaw:" -ForegroundColor Cyan
if ($script:InstallDistroAtHandoff) {
Write-Host "Ubuntu will install and launch in a separate window. After first-run setup completes, run this command inside Ubuntu to install NemoClaw:" -ForegroundColor Cyan
} else {
Write-Host "An Ubuntu window is opening. Run this command inside Ubuntu to install NemoClaw:" -ForegroundColor Cyan
}
Write-Host ''
Write-Host " $installerCommand" -ForegroundColor White
Write-Host ''
Expand All @@ -890,7 +860,11 @@ function Invoke-Main {
Ensure-UbuntuWsl
Install-DockerDesktop
Start-DockerDesktop
Ensure-DockerWslIntegration
if ($script:InstallDistroAtHandoff) {
Write-Status "Skipping Docker-in-WSL verification until $DistroName is installed and first-run setup completes."
} else {
Ensure-DockerWslIntegration
}
Write-DockerDesktopNotice
Unregister-ResumeRunOnce
Write-Status 'Windows preparation completed successfully.'
Expand Down
45 changes: 22 additions & 23 deletions test/bootstrap-windows.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,64 +57,63 @@ function runPowerShellHarness(script: string) {
}

describe("Windows bootstrap WSL distro preflight", () => {
itPowerShell("installs Ubuntu 24.04 before continuing when WSL has no registered distro", () => {
itPowerShell("defers missing Ubuntu 24.04 install to a separate handoff window", () => {
const result = runPowerShellHarness(`
$ErrorActionPreference = 'Stop'
. ${JSON.stringify(BOOTSTRAP_WINDOWS)}

$script:getDistroCalls = 0
$script:nativeCalls = @()
$script:startProcessCalls = @()
$script:statusMessages = @()

function Resolve-WslExe { return 'wsl.exe' }
function Get-WslDistros {
$script:getDistroCalls += 1
if ($script:getDistroCalls -eq 1) { return @() }
return @('Ubuntu-24.04')
}
function Get-WslDistros { return @() }
function Invoke-NativeCommand {
param([string]$FilePath, [string[]]$ArgumentList = @(), [switch]$SuppressOutput)
$script:nativeCalls += ,@($FilePath, ($ArgumentList -join ' '))
return 0
}
function Invoke-NativeCommandOutput {
param([string]$FilePath, [string[]]$ArgumentList = @(), [switch]$MergeError)
return [pscustomobject]@{ ExitCode = 0; Output = 'WSL_OK' }
function Start-Process {
param([string]$FilePath, [string[]]$ArgumentList = @())
$script:startProcessCalls += ,@($FilePath, ($ArgumentList -join ' '))
return [pscustomobject]@{}
}
function Ensure-WslDistroVersion2 { param([string]$Name) }
function Write-Status { param([string]$Message, [string]$Level = 'INFO') $script:statusMessages += $Message }

Ensure-UbuntuWsl
Open-UbuntuForInstaller

[pscustomobject]@{
nativeCalls = $script:nativeCalls
startProcessCalls = $script:startProcessCalls
statusMessages = $script:statusMessages
installDistroAtHandoff = $script:InstallDistroAtHandoff
} | ConvertTo-Json -Compress
`);

expect(result.status).toBe(0);
expect(result.stderr).toBe("");
const parsed = JSON.parse(result.stdout.trim());
expect(parsed.nativeCalls).toContainEqual(["wsl.exe", "--install Ubuntu-24.04"]);
expect(parsed.nativeCalls).toContainEqual(["wsl.exe", "--set-default Ubuntu-24.04"]);
expect(parsed.statusMessages).toContain("WSL distro registered: Ubuntu-24.04");
expect(parsed.installDistroAtHandoff).toBe(true);
expect(parsed.nativeCalls).toEqual([]);
expect(parsed.startProcessCalls).toContainEqual(["wsl.exe", "--install -d Ubuntu-24.04"]);
expect(parsed.statusMessages).toContain(
"Ubuntu-24.04 is not registered yet. It will be installed during the final Ubuntu handoff.",
);
});

itPowerShell("prints the issue 3974 guidance when Ubuntu 24.04 cannot be installed", () => {
itPowerShell("prints the issue 3974 guidance when the deferred Ubuntu launch fails", () => {
const result = runPowerShellHarness(`
$ErrorActionPreference = 'Stop'
. ${JSON.stringify(BOOTSTRAP_WINDOWS)}

function Resolve-WslExe { return 'wsl.exe' }
function Get-WslDistros { return @() }
function Invoke-NativeCommand {
param([string]$FilePath, [string[]]$ArgumentList = @(), [switch]$SuppressOutput)
return 42
}
function Start-Process { throw 'launch failed' }
function Write-Status { param([string]$Message, [string]$Level = 'INFO') Write-Host $Message }

$script:InstallDistroAtHandoff = $true
try {
Ensure-UbuntuWsl
Open-UbuntuForInstaller
Write-Host 'UNEXPECTED_SUCCESS'
exit 3
} catch {
Expand All @@ -125,8 +124,8 @@ try {
expect(result.status).toBe(0);
expect(result.stderr).toBe("");
expect(result.stdout).toContain("NemoClaw on Windows ARM requires WSL2 Ubuntu 24.04.");
expect(result.stdout).toContain("Please run: wsl --install Ubuntu-24.04");
expect(result.stdout).toContain("Please run: wsl --install -d Ubuntu-24.04");
expect(result.stdout).toContain("Then re-run this installer.");
expect(result.stdout).toContain("CAUGHT: Could not install WSL distro 'Ubuntu-24.04'.");
expect(result.stdout).toContain("CAUGHT: launch failed");
});
});
Loading