Files
wehub-resource-sync 9f2e1cd2af
Ruby / Ruby 4.0.5 (push) Failing after 5m13s
Ruby / Ruby 3.3.4 (push) Failing after 5m43s
Ruby / Ruby 2.6.8 (push) Failing after 16m2s
chore: import upstream snapshot with attribution
2026-07-13 12:33:07 +08:00

613 lines
24 KiB
PowerShell

#Requires -Version 5
# OpenClacky Windows Installation Script
#
# Usage (standard):
# powershell -c "irm https://oss.1024code.com/clacky-ai/openclacky/main/scripts/install.ps1 | iex"
#
# Usage (white-label / custom brand):
# powershell -c "& ([scriptblock]::Create((irm 'https://oss.1024code.com/clacky-ai/openclacky/main/scripts/install.ps1'))) -BrandName 'MyCLI' -CommandName 'mycli'"
#
# Parameters:
# -BrandName Display name shown in prompts (default: OpenClacky)
# -CommandName CLI command name after install (default: openclacky)
# -Region CDN region: china (default) or global (use global for non-China networks)
#
# WSL1 is preferred (shares Windows network stack — no mirrored networking needed).
# If WSL1 import fails, the script falls back to WSL2 with mirrored networking.
# If WSL is not installed at all, the script enables it and asks you to reboot.
# After rebooting, run the same command again to complete installation.
#
# Development: .\install.ps1 -Local
# Uses install.sh from the same directory as this script instead of CDN.
param(
[switch]$Local,
[string]$BrandName = "",
[string]$CommandName = "",
[ValidateSet("china", "global")]
[string]$Region = "china"
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$env:WSL_UTF8 = "1"
$global:DisplayName = if ($BrandName) { $BrandName } else { "OpenClacky" }
$global:DisplayCmd = if ($CommandName) { $CommandName } else { "openclacky" }
Write-Host "==> Region: $Region"
$CLACKY_CDN_BASE_URL = if ($Region -eq "global") { "https://sg.oss.1024code.com" } else { "https://oss.1024code.com" }
$CLACKY_INSTALL_SCRIPT_BASE_URL = if ($Region -eq "global") { "https://raw.githubusercontent.com" } else { "https://oss.1024code.com" }
$CLACKY_CDN_PRIMARY_HOST = if ($Region -eq "global") { "sg.oss.1024code.com" } else { "oss.1024code.com" }
Write-Host "==> CDN_BASE_URL: $CLACKY_CDN_BASE_URL"
Write-Host "==> INSTALL_SCRIPT_BASE_URL: $CLACKY_INSTALL_SCRIPT_BASE_URL"
Write-Host "==> CDN_PRIMARY_HOST: $CLACKY_CDN_PRIMARY_HOST"
$CLACKY_CDN_BACKUP_HOST = "clackyai-1258723534.cos.ap-guangzhou.myqcloud.com"
$INSTALL_PS1_COMMAND = "powershell -c `"irm $CLACKY_INSTALL_SCRIPT_BASE_URL/clacky-ai/openclacky/main/scripts/install.ps1 | iex`""
$INSTALL_SCRIPT_URL = "$CLACKY_INSTALL_SCRIPT_BASE_URL/clacky-ai/openclacky/main/scripts/install.sh"
$UBUNTU_WSL_AMD64_URL = "$CLACKY_CDN_BASE_URL/ubuntu-jammy-wsl-amd64-ubuntu22.04lts.rootfs.tar.gz"
$UBUNTU_WSL_AMD64_SHA256_URL = "$CLACKY_CDN_BASE_URL/ubuntu-jammy-wsl-amd64-ubuntu22.04lts.rootfs.tar.gz.sha256"
$UBUNTU_WSL_ARM64_URL = "$CLACKY_CDN_BASE_URL/ubuntu-jammy-wsl-arm64-ubuntu22.04lts.rootfs.tar.gz"
$UBUNTU_WSL_ARM64_SHA256_URL = "$CLACKY_CDN_BASE_URL/ubuntu-jammy-wsl-arm64-ubuntu22.04lts.rootfs.tar.gz.sha256"
$WSL_UPDATE_URL_X64 = "$CLACKY_CDN_BASE_URL/wsl.2.6.3.0.x64.msi" # Windows x64 (Win10+Win11)
$WSL_UPDATE_URL_ARM64 = "$CLACKY_CDN_BASE_URL/wsl.2.6.3.0.arm64.msi" # Windows ARM64
$UBUNTU_WSL_DIR = "$env:SystemDrive\WSL\Ubuntu"
# ===========================================================================
# Shared Helpers
# ===========================================================================
function Write-Info { param($msg) Write-Host " [i] $msg" -ForegroundColor Cyan }
function Write-Success { param($msg) Write-Host " [+] $msg" -ForegroundColor Green }
function Write-Warn { param($msg) Write-Host " [!] $msg" -ForegroundColor Yellow }
function Write-Fail { param($msg) Write-Host " [x] $msg" -ForegroundColor Red }
function Write-Step { param($msg) Write-Host "`n==> $msg" -ForegroundColor Blue }
# Exit codes (consumed by GUI installer to map to localized error messages).
$EXIT_OK = 0
$EXIT_GENERIC_ERROR = 1
$EXIT_NETWORK_ERROR = 2
$EXIT_DISK_ERROR = 3
$EXIT_UNSUPPORTED_OS = 4
$EXIT_NOT_ADMIN = 5
$EXIT_REBOOT_REQUIRED = 6
$EXIT_WSL_ERROR = 7
function Test-IsAdmin {
return ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
[Security.Principal.WindowsBuiltInRole]::Administrator)
}
function Get-SafeTempDir {
# Use [IO.Path]::GetTempPath() instead of $env:TEMP.
# $env:TEMP can return a short (8.3) path (e.g. C:\Users\USERNA~1\AppData\Local\Temp)
# on systems where the user profile path contains spaces or non-ASCII characters,
# which can break tools that don't handle 8.3 names correctly.
# [IO.Path]::GetTempPath() always returns the full long path.
$tempDir = [IO.Path]::GetTempPath().TrimEnd("\", "/")
return $tempDir
}
# Robust file download: try curl first (shows progress), fall back to
# Invoke-WebRequest. Returns $true on success, $false on failure.
function Invoke-Download {
param([string]$Url, [string]$OutFile)
$urls = @($Url)
try {
if (([Uri]$Url).Host -eq $CLACKY_CDN_PRIMARY_HOST) {
$urls += ([Uri]$Url).AbsoluteUri.Replace($CLACKY_CDN_PRIMARY_HOST, $CLACKY_CDN_BACKUP_HOST)
}
} catch {}
foreach ($u in $urls) {
if ($u -ne $Url) { Write-Warn "Primary download failed, retrying with backup mirror." }
try {
curl.exe -L --fail --progress-bar $u -o $OutFile
if ($LASTEXITCODE -eq 0) { return $true }
} catch {}
try {
Invoke-WebRequest -Uri $u -OutFile $OutFile -UseBasicParsing -TimeoutSec 60
return $true
} catch {}
}
return $false
}
# Verify SHA256 of a local file against a remote .sha256 file.
# Returns $true on match, or if the checksum file cannot be fetched (non-fatal).
function Test-Sha256 {
param([string]$FilePath, [string]$Sha256Url)
$safeTemp = Get-SafeTempDir
$sha256File = "$safeTemp\download.sha256"
try {
if (-not (Invoke-Download -Url $Sha256Url -OutFile $sha256File)) {
Write-Warn "Could not download checksum file — skipping verification."
return $true
}
$expectedLine = (Get-Content $sha256File -Raw).Trim()
$expected = ($expectedLine -split '\s+')[0].ToLower()
$actual = (Get-FileHash -Algorithm SHA256 -Path $FilePath).Hash.ToLower()
if ($actual -ne $expected) {
Write-Fail "Checksum mismatch!"
Write-Fail " Expected : $expected"
Write-Fail " Got : $actual"
return $false
}
Write-Success "Checksum OK."
return $true
} finally {
Remove-Item -Force -ErrorAction SilentlyContinue $sha256File
}
}
# Use cmd.exe to avoid PS5 NativeCommandError and UTF-16LE mojibake on stderr.
# exit 1 = WSL feature not enabled; exit 0 = WSL is functional.
# Timeout 10s to avoid hanging when WSL is partially initialised.
function Invoke-WslStatusExitCode {
# Returns the exit code of `wsl --status`.
# exit 0 = WSL fully enabled (Win11 / distro installed)
# exit 1 = WSL feature not enabled
# exit -1 = WSL enabled, no distro installed (Win10 wsl --list)
# exit -444 = WSL enabled, no distro installed (Win10 wsl --status)
# Only exit 1 means "WSL is not set up". All other codes mean WSL is functional.
# Timeout (10s) is treated as exit 1 (WSL completely unresponsive).
#
# Note: Start-Process + -Redirect* loses ExitCode. Use System.Diagnostics.Process directly.
$psi = New-Object System.Diagnostics.ProcessStartInfo
$psi.FileName = "wsl.exe"
$psi.Arguments = "--status"
$psi.UseShellExecute = $false
$psi.RedirectStandardOutput = $true
$psi.RedirectStandardError = $true
try { $p = [System.Diagnostics.Process]::Start($psi) } catch { return 1 }
$finished = $p.WaitForExit(10000) # 10 seconds
if (-not $finished) {
$p.Kill()
Write-Info "WSL --status timed out (WSL not ready)."
return 1
}
return $p.ExitCode
}
# Returns $true if a distro named exactly "Ubuntu" is registered.
# wsl --list outputs UTF-16LE regardless of WSL_UTF8; temporarily clear it and switch
# OutputEncoding to Unicode so the output decodes correctly.
function Test-UbuntuInstalled {
$prevEnc = [Console]::OutputEncoding
$prevUtf8 = $env:WSL_UTF8
[Console]::OutputEncoding = [System.Text.Encoding]::Unicode
$env:WSL_UTF8 = $null
try {
$out = (wsl.exe --list --quiet 2>$null) -join "`n"
} finally {
[Console]::OutputEncoding = $prevEnc
$env:WSL_UTF8 = $prevUtf8
}
# Whole-line match to avoid false positives from Ubuntu-22.04, Ubuntu-24.04, etc.
return ($out -match '(?im)^Ubuntu\s*$')
}
# Returns 'arm64' or 'amd64'
function Get-CpuArch {
$arch = (Get-CimInstance Win32_Processor).Architecture
# 12 = ARM64
if ($arch -eq 12) { return "arm64" }
return "amd64"
}
function Prompt-Reboot {
Write-Host ""
Write-Warn "Please restart your computer."
Write-Warn "After restarting, run the same command again:"
Write-Host " $INSTALL_PS1_COMMAND" -ForegroundColor Yellow
Write-Host ""
Read-Host "Press Enter to exit"
exit $EXIT_REBOOT_REQUIRED
}
# Download Ubuntu rootfs and verify checksum. Returns local tar path.
function Get-UbuntuRootfs {
$cpuArch = Get-CpuArch
Write-Info "CPU architecture: $cpuArch"
if ($cpuArch -eq "arm64") {
$wslUrl = $UBUNTU_WSL_ARM64_URL
$sha256Url = $UBUNTU_WSL_ARM64_SHA256_URL
} else {
$wslUrl = $UBUNTU_WSL_AMD64_URL
$sha256Url = $UBUNTU_WSL_AMD64_SHA256_URL
}
$safeTemp = Get-SafeTempDir
$tarPath = "$safeTemp\ubuntu-wsl-$cpuArch.tar.gz"
$installDir = $UBUNTU_WSL_DIR
# Disk space check (~4 GB needed: 350 MB download + ~1.5 GB imported + buffer)
$drive = Split-Path -Qualifier $installDir
$freeBytes = (Get-PSDrive ($drive.TrimEnd(':'))).Free
if ($freeBytes -lt 4GB) {
Write-Fail "Not enough disk space on $drive."
Write-Fail " Available : $([math]::Round($freeBytes / 1GB, 1)) GB"
Write-Fail " Required : ~4 GB"
exit $EXIT_DISK_ERROR
}
# Check if a valid cached tarball exists (skip download if checksum passes)
$needDownload = $true
if (Test-Path $tarPath) {
Write-Info "Found cached Ubuntu rootfs, verifying checksum..."
if (Test-Sha256 -FilePath $tarPath -Sha256Url $sha256Url) {
Write-Success "Cache valid — skipping download."
$needDownload = $false
} else {
Write-Warn "Cache corrupted — re-downloading..."
Remove-Item -Force $tarPath
}
}
try {
if ($needDownload) {
Write-Step "Downloading Ubuntu rootfs (~350 MB)..."
if (-not (Invoke-Download -Url $wslUrl -OutFile $tarPath)) {
Write-Fail "Failed to download Ubuntu rootfs. Check your network and try again."
exit $EXIT_NETWORK_ERROR
}
Write-Success "Download complete."
Write-Step "Verifying checksum..."
if (-not (Test-Sha256 -FilePath $tarPath -Sha256Url $sha256Url)) {
Write-Fail "The downloaded file is corrupted. Please try again."
exit $EXIT_NETWORK_ERROR
}
}
return $tarPath
} finally {
# Keep the tarball as cache for future runs (e.g. after reboot)
if (Test-Path $tarPath) {
Write-Info "Keeping Ubuntu rootfs cache at $tarPath for future use."
}
}
}
# Import Ubuntu rootfs into WSL.
# $WslVersion: 1 or 2
function Install-UbuntuRootfs {
param([int]$WslVersion, [string]$TarPath = "")
if (-not $TarPath) {
$TarPath = Get-UbuntuRootfs
}
Write-Step "Importing Ubuntu into WSL$WslVersion (this may take a minute)..."
New-Item -ItemType Directory -Force -Path $UBUNTU_WSL_DIR | Out-Null
$wslOutput = wsl.exe --import Ubuntu $UBUNTU_WSL_DIR $TarPath --version $WslVersion 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Fail "wsl --import failed (exit $LASTEXITCODE)."
if ($wslOutput) { Write-Fail "$wslOutput" }
Write-Fail "Try removing $UBUNTU_WSL_DIR and running the script again."
exit $EXIT_WSL_ERROR
}
Write-Success "Ubuntu (WSL$WslVersion) imported successfully."
}
function Test-WslNetwork {
Write-Info "Checking WSL network connectivity..."
wsl.exe -d Ubuntu -u root -- bash -c "curl -fsSL --max-time 3 --retry 1 $INSTALL_SCRIPT_URL -o /dev/null 2>/dev/null" | Out-Null
if ($LASTEXITCODE -ne 0) {
Write-Fail "WSL cannot reach $CLACKY_CDN_PRIMARY_HOST (curl exit $LASTEXITCODE)."
Write-Fail "Please fix the network inside WSL and re-run this installer."
exit $EXIT_NETWORK_ERROR
}
Write-Success "WSL network OK."
}
# Install OpenClacky inside the Ubuntu WSL distro.
function Run-InstallInWsl {
Write-Step "Installing $DisplayName inside WSL..."
if ($Local) {
# Convert Windows path to WSL path (e.g. C:\foo\bar -> /mnt/c/foo/bar)
$scriptDir = Split-Path -Parent $MyInvocation.PSCommandPath
$localScript = Join-Path $scriptDir "install.sh"
if (-not (Test-Path $localScript)) {
Write-Fail "Local mode: install.sh not found at $localScript"
exit $EXIT_GENERIC_ERROR
}
$wslPath = ($localScript -replace '\', '/') -replace '^([A-Za-z]):', { '/mnt/' + $args[0].Groups[1].Value.ToLower() }
Write-Info "Local mode: using $wslPath"
wsl.exe -d Ubuntu -u root -- bash $wslPath --brand-name=$BrandName --command=$CommandName
} else {
Test-WslNetwork
wsl.exe -d Ubuntu -u root -- bash -c "cd ~ && curl -fsSL $INSTALL_SCRIPT_URL | bash -s -- --brand-name=$BrandName --command=$CommandName"
}
if ($LASTEXITCODE -eq 2) {
exit $EXIT_NETWORK_ERROR
}
if ($LASTEXITCODE -ne 0) {
Write-Fail "Installation failed inside WSL (exit $LASTEXITCODE)."
Write-Fail "You can retry manually:"
Write-Host " wsl -d Ubuntu -u root -- bash -c `"curl -fsSL $INSTALL_SCRIPT_URL | bash`"" -ForegroundColor Yellow
exit $EXIT_GENERIC_ERROR
}
}
# Configure WSL2 mirrored networking so WSL can reach Windows localhost ports
# (e.g. Chrome/Edge remote debugging on 127.0.0.1:9222).
# Requires Win11 22H2+ (Build >= 22621). Silently skips on older Windows.
function Set-Wsl2MirroredNetworking {
try {
$build = [System.Environment]::OSVersion.Version.Build
if ($build -lt 22621) {
Write-Warn "Windows Build $build < 22621, skipping WSL2 mirrored networking."
return
}
$wslConfig = "$env:USERPROFILE\.wslconfig"
$content = if (Test-Path $wslConfig) { Get-Content $wslConfig -Raw } else { "" }
if ($content -match "networkingMode\s*=\s*mirrored") {
Write-Info "WSL2 mirrored networking already configured."
return
}
Write-Step "Configuring WSL2 mirrored networking..."
Add-Content $wslConfig "`n[wsl2]`nnetworkingMode=mirrored"
wsl.exe --shutdown
Write-Success "WSL2 mirrored networking enabled."
} catch {
Write-Warn "Failed to configure WSL2 mirrored networking: $_"
}
}
function Show-PostInstall {
param([int]$WslVersion)
Write-Host ""
Write-Success "$DisplayName installed successfully (WSL$WslVersion)."
Write-Host ""
Write-Info "To use $DisplayName, first enter WSL:"
Write-Host " wsl" -ForegroundColor Green
Write-Host ""
Write-Info "Then run ${DisplayName}:"
Write-Host " $DisplayCmd" -ForegroundColor Green
Write-Host ""
Write-Info "Or start the Web UI:"
Write-Host " $DisplayCmd server" -ForegroundColor Green
Write-Host " Then open http://localhost:7070 in your browser"
Write-Host ""
}
# ===========================================================================
# Registry helpers (HKCU:\Software\OpenClacky\Install)
# ===========================================================================
$REG_ROOT = "HKCU:\Software\OpenClacky\Install"
function Get-InstallReg {
param([string]$Name, $Default = $null)
try {
$val = (Get-ItemProperty -Path $REG_ROOT -Name $Name -ErrorAction Stop).$Name
return $val
} catch {
return $Default
}
}
function Set-InstallReg {
param([string]$Name, $Value)
if (-not (Test-Path $REG_ROOT)) {
New-Item -Path $REG_ROOT -Force | Out-Null
}
Set-ItemProperty -Path $REG_ROOT -Name $Name -Value $Value
}
function Remove-InstallReg {
param([string]$Name)
try {
Remove-ItemProperty -Path $REG_ROOT -Name $Name -ErrorAction Stop
} catch {}
}
# ===========================================================================
# WSL2 Path — preferred, requires hardware virtualisation
# ===========================================================================
# Returns $true if WSL2 can import the real Ubuntu rootfs.
function Test-VirtualisationSupported {
param([string]$TarPath)
Write-Info "Probing WSL2 availability..."
$safeTemp = Get-SafeTempDir
$probeName = "Wsl2Probe-$([guid]::NewGuid().ToString('N'))"
$probeDir = "$safeTemp\$probeName"
$ok = $false
try {
New-Item -ItemType Directory -Force -Path $probeDir | Out-Null
Write-Info "[probe] Running: wsl --import $probeName $probeDir $TarPath --version 2"
$probeOutput = wsl.exe --import $probeName $probeDir $TarPath --version 2 2>&1
if ($probeOutput) { Write-Info "WSL2 probe: $probeOutput" }
$importExit = $LASTEXITCODE
Write-Info "[probe] wsl --import exit code: $importExit"
$ok = ($importExit -eq 0)
} catch {
Write-Info "[probe] Exception caught: $_"
$ok = $false
} finally {
wsl.exe --unregister $probeName 2>$null | Out-Null
Write-Info "[probe] $probeName unregistered."
Remove-Item -Force -Recurse -ErrorAction SilentlyContinue $probeDir
}
Write-Info "[probe] Final result: ok=$ok"
if ($ok) {
Write-Info "WSL2 probe passed — using WSL2."
} else {
Write-Info "WSL2 probe failed (Hyper-V not available)."
}
return $ok
}
# Download and install the WSL2 kernel MSI from our CDN.
function Install-WslKernel {
param([switch]$Repair)
$cpuArch = Get-CpuArch
# Select the correct MSI for this CPU architecture.
if ($cpuArch -eq "arm64") {
$url = $WSL_UPDATE_URL_ARM64
} else {
$url = $WSL_UPDATE_URL_X64
}
$safeTemp = Get-SafeTempDir
$msiPath = "$safeTemp\wsl_update.msi"
Write-Step "Downloading WSL kernel update ($cpuArch)..."
if (-not (Invoke-Download -Url $url -OutFile $msiPath)) {
Write-Fail "Failed to download WSL kernel update. Check your network and try again."
exit $EXIT_NETWORK_ERROR
}
if ($Repair) {
# /fa = force repair all files; handles corrupt/partial installs where /i silently no-ops.
Start-Process msiexec -Wait -ArgumentList "/fa", $msiPath, "/quiet", "/norestart"
Remove-Item -Force -ErrorAction SilentlyContinue $msiPath
Write-Success "WSL kernel repair attempted."
return
}
Write-Info "Installing WSL kernel..."
Start-Process msiexec -Wait -ArgumentList "/i", $msiPath, "/quiet", "/norestart"
Write-Success "WSL kernel installed."
Remove-Item -Force -ErrorAction SilentlyContinue $msiPath
}
# Enable WSL + VirtualMachinePlatform features, install kernel MSI, then reboot.
function Enable-WslFeatures {
Write-Step "Enabling WSL components..."
dism /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart | Out-Null
dism /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart | Out-Null
Write-Success "WSL components enabled."
Install-WslKernel
Set-InstallReg -Name "WslFeaturesEnabled" -Value "1"
Set-InstallReg -Name "InstallPhase" -Value "wsl-pending"
Prompt-Reboot
}
# ===========================================================================
# Main
# ===========================================================================
Write-Host ""
Write-Host "$DisplayName Installation Script (Windows)" -ForegroundColor Cyan
Write-Host ""
if (-not (Test-IsAdmin)) {
Write-Fail "Please re-run this script as Administrator:"
Write-Host ""
Write-Host " Right-click PowerShell -> 'Run as administrator', then:" -ForegroundColor Yellow
Write-Host " $INSTALL_PS1_COMMAND" -ForegroundColor Yellow
exit $EXIT_NOT_ADMIN
}
# Check minimum Windows version: WSL1 requires Build 16215 (Win10 1709).
$osBuild = [System.Environment]::OSVersion.Version.Build
if ($osBuild -lt 16215) {
Write-Fail "Unsupported Windows version (Build $osBuild)."
Write-Fail "WSL requires Windows 10 Build 16215 (version 1709) or later."
Write-Fail "Please update Windows and try again."
exit $EXIT_UNSUPPORTED_OS
}
Write-Info "Windows Build $osBuild — OK."
# Step 1: Ensure WSL feature is enabled (same for WSL1 and WSL2)
Write-Step "Checking WSL status..."
$wslCode = Invoke-WslStatusExitCode
Write-Info "WSL --status exit code: $wslCode"
$installPhase = Get-InstallReg -Name "InstallPhase" -Default ""
$wslFeaturesEnabled = Get-InstallReg -Name "WslFeaturesEnabled" -Default ""
$kernelRepairAttempted = Get-InstallReg -Name "KernelRepairAttempted" -Default ""
Write-Info "InstallPhase: '$installPhase'"
Write-Info "WslFeaturesEnabled: '$wslFeaturesEnabled'"
Write-Info "KernelRepairAttempted: '$kernelRepairAttempted'"
if ($installPhase -eq "" -and $wslCode -ne 0) {
# First run and WSL not ready: enable WSL features and reboot.
Enable-WslFeatures
# Always exits (prompts reboot)
}
# phase == wsl-pending + code 1: reboot happened but WSL still not ready; try kernel repair once.
if ($installPhase -eq "wsl-pending" -and $wslCode -eq 1) {
if ($kernelRepairAttempted -ne "1") {
Write-Warn "WSL features were enabled but WSL is still not ready. Attempting kernel repair..."
Set-InstallReg -Name "KernelRepairAttempted" -Value "1"
Install-WslKernel -Repair
Prompt-Reboot
} else {
Write-Warn "WSL kernel repair was already attempted but WSL is still not ready."
exit $EXIT_WSL_ERROR
}
}
# wslCode != 1 (0, -1, -444, 50, etc.): WSL is functional, continue.
Remove-InstallReg -Name "InstallPhase"
# Step 2: Install Ubuntu, preferring WSL1 (shares Windows network — no mirrored needed).
# If WSL1 import fails, fall back to WSL2.
# If the distro already exists, keep whatever version was previously installed.
if (Test-UbuntuInstalled) {
Write-Info "Ubuntu (WSL) already installed — skipping import."
$wslVersion = Get-InstallReg -Name "WslVersion" -Default 2
} else {
$tarPath = Get-UbuntuRootfs
# Try WSL1 first
Write-Info "Attempting WSL1 import..."
$wsl1Ok = $false
try {
New-Item -ItemType Directory -Force -Path $UBUNTU_WSL_DIR | Out-Null
wsl.exe --import Ubuntu $UBUNTU_WSL_DIR $tarPath --version 1 >$null 2>$null
$wsl1Ok = ($LASTEXITCODE -eq 0)
} catch {
$wsl1Ok = $false
}
if ($wsl1Ok) {
Write-Success "Ubuntu (WSL1) imported successfully."
$wslVersion = 1
} else {
# Clean up failed WSL1 attempt
wsl.exe --unregister Ubuntu 2>$null | Out-Null
Remove-Item -Force -Recurse -ErrorAction SilentlyContinue $UBUNTU_WSL_DIR
Write-Info "WSL1 import failed, trying WSL2..."
if (Test-VirtualisationSupported -TarPath $tarPath) {
wsl.exe --set-default-version 2 >$null 2>$null
Install-UbuntuRootfs -WslVersion 2 -TarPath $tarPath
$wslVersion = 2
} else {
if ($wslFeaturesEnabled -ne "1") {
Write-Warn "Neither WSL1 nor WSL2 is available. Enabling WSL components..."
Enable-WslFeatures
# Always exits (prompts reboot)
}
Write-Fail "Failed to import Ubuntu into both WSL1 and WSL2."
Write-Fail "Please ensure Windows Subsystem for Linux is enabled and try again."
exit $EXIT_WSL_ERROR
}
}
}
Write-Success "WSL is ready."
Run-InstallInWsl
# For WSL2, configure mirrored networking AFTER install.sh succeeds (NAT is more
# reliable for outbound traffic during installation). The shutdown here is safe
# because installation is already complete.
if ($wslVersion -eq 2) { Set-Wsl2MirroredNetworking }
Set-InstallReg -Name "WslVersion" -Value $wslVersion
Show-PostInstall -WslVersion $wslVersion