Files
planet/scripts/windows/planet-launcher.ps1
rayd1o a2025dbfe0
Some checks failed
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
release / images (push) Has been cancelled
ci / delivery (push) Has been cancelled
release: bump version to 0.75.2
2026-09-25 02:03:53 +08:00

197 lines
8.6 KiB
PowerShell

param(
[string]$Distro,
[string]$LinuxProjectRoot,
[switch]$FromDesktop,
[switch]$InstallOnly,
[switch]$Elevated
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
$script:PlanetTargetFingerprintLength = 12
function Invoke-WslText {
param([string[]]$Arguments)
$output = & wsl.exe @Arguments
if ($LASTEXITCODE -ne 0) {
throw "WSL command failed (exit $LASTEXITCODE). Check the distribution and project directory."
}
return ($output -join "`n").Trim()
}
function Get-PlanetTarget {
param([string]$WindowsRoot)
if ($WindowsRoot -match '^\\\\(?:wsl\$|wsl\.localhost)\\([^\\]+)(\\.*)?$') {
$root = if ($Matches[2]) { $Matches[2].Replace('\', '/') } else { '/' }
return @{ Distro = $Matches[1]; LinuxProjectRoot = $root.TrimEnd('/') }
}
if ($WindowsRoot -notmatch '^[a-zA-Z]:\\') {
throw 'Run planet.cmd from a WSL project folder or a local Windows drive.'
}
$distribution = Invoke-WslText -Arguments @('--exec', 'printenv', 'WSL_DISTRO_NAME')
$root = Invoke-WslText -Arguments @('-d', $distribution, '--exec', 'wslpath', '-a', '-u', $WindowsRoot)
return @{ Distro = $distribution; LinuxProjectRoot = $root.TrimEnd('/') }
}
function Get-PlanetWindowsRoot {
param([hashtable]$Target)
if ([string]::IsNullOrWhiteSpace($Target.Distro) -or
$Target.Distro -match '[\\/\r\n]' -or
$Target.LinuxProjectRoot -notmatch '^/' -or
$Target.LinuxProjectRoot -match '[\r\n]') {
throw 'Invalid WSL distribution or absolute project path.'
}
return '\\wsl$\' + $Target.Distro + $Target.LinuxProjectRoot.Replace('/', '\')
}
function Assert-PlanetTarget {
param([hashtable]$Target)
$windowsRoot = Get-PlanetWindowsRoot $Target
foreach ($relative in @('planet.sh', 'scripts\windows\planet-launcher.ps1', 'scripts\windows\restart.zsh')) {
if (-not (Test-Path -LiteralPath (Join-Path $windowsRoot $relative) -PathType Leaf)) {
throw "P_WINDOWS_LAUNCHER_TARGET_MISSING: $windowsRoot\$relative. Run planet.cmd from the current project folder to regenerate the desktop launcher."
}
}
}
function Get-PlanetBootstrap {
param([hashtable]$Target, [switch]$RunElevated)
# Only base64 crosses CMD/PowerShell command-line parsing; paths are data.
$json = $Target | ConvertTo-Json -Compress
$payload = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($json))
$elevatedFlag = if ($RunElevated) { '$true' } else { '$false' }
return @"
`$ErrorActionPreference = 'Stop'
`$ProgressPreference = 'SilentlyContinue'
try {
`$target = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('$payload')) | ConvertFrom-Json
`$root = '\\wsl$\' + `$target.Distro + `$target.LinuxProjectRoot.Replace('/', '\')
`$helper = Join-Path `$root 'scripts\windows\planet-launcher.ps1'
if (-not (Test-Path -LiteralPath `$helper -PathType Leaf)) {
throw 'P_WINDOWS_LAUNCHER_TARGET_MISSING: Project moved or unavailable. Run planet.cmd from the current project folder again.'
}
& `$helper -Distro `$target.Distro -LinuxProjectRoot `$target.LinuxProjectRoot -FromDesktop -Elevated:$elevatedFlag -InstallOnly:(`$env:PLANET_LAUNCHER_INSTALL_ONLY -eq '1')
exit `$LASTEXITCODE
} catch {
Write-Host `$_.Exception.Message -ForegroundColor Red
exit 1
}
"@
}
function ConvertTo-PlanetEncodedCommand {
param([string]$Command)
return [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($Command))
}
function Install-PlanetDesktopLauncher {
param([hashtable]$Target, [string]$Desktop = [Environment]::GetFolderPath('DesktopDirectory'))
if (-not $Desktop -or -not (Test-Path -LiteralPath $Desktop -PathType Container)) {
throw 'Windows Desktop folder is unavailable.'
}
$identity = $Target.Distro + "`n" + $Target.LinuxProjectRoot
$sha256 = [Security.Cryptography.SHA256]::Create()
try {
$identityBytes = [Text.Encoding]::UTF8.GetBytes($identity)
$fingerprint = [BitConverter]::ToString($sha256.ComputeHash($identityBytes)).Replace('-', '')
$hash = $fingerprint.Substring(0, $script:PlanetTargetFingerprintLength)
} finally {
$sha256.Dispose()
}
$marker = "rem Planet managed launcher v1 target=$hash"
$destination = Join-Path $Desktop 'Planet.cmd'
if ((Test-Path -LiteralPath $destination) -and
-not ([IO.File]::ReadAllText($destination).Contains($marker))) {
$destination = Join-Path $Desktop "Planet-$hash.cmd"
}
if ((Test-Path -LiteralPath $destination) -and
-not ([IO.File]::ReadAllText($destination).Contains($marker))) {
throw "Refusing to overwrite an unrelated file: $destination"
}
$encoded = ConvertTo-PlanetEncodedCommand (Get-PlanetBootstrap $Target)
$lines = @(
'@echo off', 'setlocal EnableExtensions DisableDelayedExpansion', $marker,
'set "PLANET_LAUNCHER_INSTALL_ONLY="',
'if /i "%~1"=="--install-only" set "PLANET_LAUNCHER_INSTALL_ONLY=1"',
'set "PLANET_POWERSHELL=powershell.exe"',
'where.exe pwsh.exe >nul 2>&1',
'if not errorlevel 1 set "PLANET_POWERSHELL=pwsh.exe"',
"%PLANET_POWERSHELL% -NoLogo -NoProfile -ExecutionPolicy Bypass -EncodedCommand $encoded",
'set "planet_launcher_exit=%errorlevel%"',
'if not "%planet_launcher_exit%"=="0" pause',
'exit /b %planet_launcher_exit%'
)
[IO.File]::WriteAllText($destination, ($lines -join "`r`n") + "`r`n", [Text.Encoding]::ASCII)
return $destination
}
function Test-PlanetAdministrator {
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal($identity)
return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}
function Get-PlanetTerminalLaunch {
param([string]$EncodedCommand)
$shell = Get-Command pwsh.exe -CommandType Application -ErrorAction SilentlyContinue |
Select-Object -First 1
if (-not $shell) {
$shell = Get-Command powershell.exe -CommandType Application -ErrorAction Stop |
Select-Object -First 1
}
$shellArguments = "-NoLogo -NoProfile -NoExit -ExecutionPolicy Bypass -EncodedCommand $EncodedCommand"
$launch = @{ FilePath = $shell.Source; ArgumentList = $shellArguments; WindowStyle = 'Normal' }
$terminal = Get-Command wt.exe -CommandType Application -ErrorAction SilentlyContinue |
Select-Object -First 1
if ($terminal) {
$launch.FilePath = $terminal.Source
# Start-Process joins arguments: quote the executable path explicitly.
$launch.ArgumentList = '-w new new-tab --title Planet "' + $shell.Source + '" ' + $shellArguments
}
return $launch
}
function Start-PlanetRestart {
param([hashtable]$Target, [switch]$AlreadyElevated)
$script:PlanetLauncherExitCode = 0
$isAdministrator = Test-PlanetAdministrator
if (-not $AlreadyElevated) {
$encoded = ConvertTo-PlanetEncodedCommand (Get-PlanetBootstrap $Target -RunElevated)
$launch = Get-PlanetTerminalLaunch $encoded
if (-not $isAdministrator) { $launch.Verb = 'RunAs' }
# An interactive terminal is intentional: startup logs and the WSL shell stay visible.
Start-Process @launch | Out-Null
return
}
if (-not $isAdministrator) { throw 'Administrator elevation was not granted.' }
Write-Host "Restarting Planet in $($Target.Distro):$($Target.LinuxProjectRoot)"
& wsl.exe -d $Target.Distro --cd $Target.LinuxProjectRoot --exec zsh -l -i ($Target.LinuxProjectRoot + '/scripts/windows/restart.zsh')
$script:PlanetLauncherExitCode = $LASTEXITCODE
}
if ($MyInvocation.InvocationName -ne '.') {
try {
[Console]::OutputEncoding = New-Object Text.UTF8Encoding($false)
$OutputEncoding = [Console]::OutputEncoding
if ($FromDesktop) {
$target = @{ Distro = $Distro; LinuxProjectRoot = $LinuxProjectRoot }
} else {
$root = (Get-Item -LiteralPath (Join-Path $PSScriptRoot '..\..')).FullName
$target = Get-PlanetTarget $root
}
Assert-PlanetTarget $target
if (-not $FromDesktop) {
$desktopLauncher = Install-PlanetDesktopLauncher $target
Write-Host "Desktop launcher ready: $desktopLauncher"
}
if ($InstallOnly) { exit 0 }
# Do not capture the WSL output: the interactive terminal must stream live.
Start-PlanetRestart $target -AlreadyElevated:$Elevated
exit $script:PlanetLauncherExitCode
} catch {
Write-Host $_.Exception.Message -ForegroundColor Red
exit 1
}
}