diff --git a/docs/get-started/windows-preparation.mdx b/docs/get-started/windows-preparation.mdx index 5291642d33c..ec9138e1d8f 100644 --- a/docs/get-started/windows-preparation.mdx +++ b/docs/get-started/windows-preparation.mdx @@ -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: @@ -74,7 +76,7 @@ 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. @@ -82,7 +84,7 @@ Let the distribution launch and complete first-run setup (pick a Unix username a 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. Verify the distribution is registered and running WSL 2: @@ -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 diff --git a/scripts/bootstrap-windows.ps1 b/scripts/bootstrap-windows.ps1 index b6cbeee29e2..de964361d73 100644 --- a/scripts/bootstrap-windows.ps1 +++ b/scripts/bootstrap-windows.ps1 @@ -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( @@ -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 { @@ -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 @@ -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 @@ -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) { @@ -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 + } } } @@ -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 '' @@ -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.' diff --git a/test/bootstrap-windows.test.ts b/test/bootstrap-windows.test.ts index bcf9682ec9b..77f14d15a99 100644 --- a/test/bootstrap-windows.test.ts +++ b/test/bootstrap-windows.test.ts @@ -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 { @@ -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"); }); });