# Matrix light-client installer for Windows (C9). ONE copy-paste from the web: # # irm /matrix.ps1 | iex; matrix-install -Token XXXX -Cloud # # (the script DEFINES the matrix-install function — iex loads it, then you # # call it; saved-file usage also works: .\matrix.ps1 -Token ... -Cloud ...) # # Mirrors scripts/install.sh: user-level Node >=22.5 (downloaded when missing), # bundle to ~\.matrix\versions\, 'current' junction, matrix.cmd launcher on # the USER PATH, then hands the one-time token to `matrix onboard`. # NO admin rights, loud failures. # # ⚠ 诚实标注: SQLite WAL on NTFS is functionally supported but NOT yet load- # verified by us on a real Windows machine (master plan §7.2 P2 item) — treat # the first Windows deployment as a validation run. function matrix-install { param( [Parameter(Mandatory = $true)][string]$Token, [Parameter(Mandatory = $true)][string]$Cloud, [switch]$Headless, [switch]$Server ) $ErrorActionPreference = 'Stop' function Fail($msg) { Write-Error "[matrix-install] $msg"; exit 1 } function Log($msg) { Write-Host "[matrix-install] $msg" } $Cloud = $Cloud.TrimEnd('/') $MatrixHome = Join-Path $env:USERPROFILE '.matrix' $NodeVersion = if ($env:MATRIX_NODE_VERSION) { $env:MATRIX_NODE_VERSION } else { 'v22.11.0' } # ---- 1. Node runtime (>=22.5) ----------------------------------------------- function Test-NodeOk($bin) { try { & $bin -e 'var v=process.versions.node.split(".").map(Number);process.exit((v[0]>22||(v[0]===22&&v[1]>=5))?0:1)' 2>$null; return $LASTEXITCODE -eq 0 } catch { return $false } } $Node = $null $sys = Get-Command node -ErrorAction SilentlyContinue if ($sys -and (Test-NodeOk $sys.Source)) { $Node = $sys.Source Log "using system Node: $Node" } else { $plat = if ([Environment]::Is64BitOperatingSystem) { 'win-x64' } else { Fail 'unsupported: 32-bit Windows' } if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { $plat = 'win-arm64' } $zipName = "node-$NodeVersion-$plat.zip" $rt = Join-Path $MatrixHome 'runtime' New-Item -ItemType Directory -Force -Path $rt | Out-Null $tmp = Join-Path $env:TEMP "matrix-node-$([guid]::NewGuid().ToString('n')).zip" Log "system Node missing or < 22.5 — downloading Node $NodeVersion ($plat, user-level)" Invoke-WebRequest -Uri "https://nodejs.org/dist/$NodeVersion/$zipName" -OutFile $tmp $dest = Join-Path $rt 'node' if (Test-Path $dest) { Remove-Item -Recurse -Force $dest } Expand-Archive -Path $tmp -DestinationPath $rt -Force Rename-Item -Path (Join-Path $rt "node-$NodeVersion-$plat") -NewName 'node' Remove-Item $tmp -Force $Node = Join-Path $dest 'node.exe' if (-not (Test-NodeOk $Node)) { Fail 'downloaded node failed the >=22.5 check' } Log "installed Node runtime at $Node" } # ---- 2. client bundle --------------------------------------------------------- Log 'downloading client bundle' $tmpDir = Join-Path $env:TEMP "matrix-bundle-$([guid]::NewGuid().ToString('n'))" New-Item -ItemType Directory -Force -Path $tmpDir | Out-Null Invoke-WebRequest -Uri "$Cloud/client-bundle.tar.gz" -OutFile (Join-Path $tmpDir 'bundle.tar.gz') $ver = $null try { $ver = (Invoke-RestMethod -Uri "$Cloud/latest.json").version } catch { } $x = Join-Path $tmpDir 'x' New-Item -ItemType Directory -Force -Path $x | Out-Null tar -xzf (Join-Path $tmpDir 'bundle.tar.gz') -C $x # bsdtar ships with Windows 10+ if (-not $ver) { try { $ver = (Get-Content (Join-Path $x 'VERSION') -Raw).Trim() } catch { $ver = Get-Date -Format 'yyyyMMddHHmmss' } } if (-not (Test-Path (Join-Path $x 'services/control-plane/src/server.mjs'))) { Fail 'bad bundle: missing server.mjs' } $vdir = Join-Path (Join-Path $MatrixHome 'versions') $ver if (Test-Path $vdir) { Remove-Item -Recurse -Force $vdir } New-Item -ItemType Directory -Force -Path (Split-Path $vdir) | Out-Null Move-Item -Path $x -Destination $vdir Remove-Item -Recurse -Force $tmpDir $current = Join-Path $MatrixHome 'current' if (Test-Path $current) { Remove-Item -Force -Recurse $current } New-Item -ItemType Junction -Path $current -Target $vdir | Out-Null # junction = no admin needed Log "installed bundle -> $vdir (current -> $ver)" # ---- 3. launcher on the user PATH --------------------------------------------- $bin = Join-Path $MatrixHome 'bin' New-Item -ItemType Directory -Force -Path $bin | Out-Null $cmd = Join-Path $bin 'matrix.cmd' @" @echo off set MATRIX_BUNDLE_ROOT=%USERPROFILE%\.matrix\current "$Node" "%USERPROFILE%\.matrix\current\workers\matrix-worker\src\cli.mjs" %* "@ | Set-Content -Path $cmd -Encoding ascii $userPath = [Environment]::GetEnvironmentVariable('Path', 'User') if ($userPath -notlike "*$bin*") { [Environment]::SetEnvironmentVariable('Path', "$bin;$userPath", 'User') Log "added $bin to your user PATH (new terminals pick it up)" } $env:Path = "$bin;$env:Path" Log "wrote launcher $cmd" # ---- 4. onboard ---------------------------------------------------------------- $onboardArgs = @('onboard', '--cloud', $Cloud, '--token', $Token) if ($Headless) { $onboardArgs += '--headless' } if ($Server) { $onboardArgs += '--server' } Log "launching: matrix $($onboardArgs -join ' ')" & $cmd @onboardArgs exit $LASTEXITCODE } # Saved-file usage (.\matrix.ps1 -Token ... -Cloud ...): forward the args. # Under `irm | iex` there are no args — the function is simply defined for the # documented second half of the one-liner. if ($args.Count -gt 0) { matrix-install @args }