From 3d1d7b5b8766723971b25036eac6a2640c7836b8 Mon Sep 17 00:00:00 2001 From: drakezhang Date: Wed, 9 Sep 2026 18:33:02 +0800 Subject: [PATCH] fix(installer): stop existing daemon when installing to a new directory --- install.ps1 | 11 ++++++----- scripts/install-windows.test.ps1 | 25 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/install.ps1 b/install.ps1 index d7b9557..a344a5c 100644 --- a/install.ps1 +++ b/install.ps1 @@ -132,18 +132,19 @@ function Install-Binary { $staged = "$Target.install-$([Guid]::NewGuid().ToString('N'))" try { [System.IO.File]::Copy($Source, $staged) + # The running daemon may come from another installation directory. + # Use the downloaded CLI: older versions have broken Windows liveness checks. + # Stop verifies daemon identity and uses the current BSK_HOME. + & $Source daemon stop + if ($LASTEXITCODE -ne 0) { throw "could not stop bsk daemon; installation was not changed" } if ([System.IO.File]::Exists($Target)) { - # Use the downloaded CLI: older versions have broken Windows - # liveness checks. Stop verifies daemon identity before terminating it. - & $Source daemon stop - if ($LASTEXITCODE -ne 0) { throw "could not stop bsk daemon; existing installation was not replaced" } # PowerShell 5.1 converts $null to an empty path for string parameters. [System.IO.File]::Replace($staged, $Target, [NullString]::Value) - Write-Log "daemon will restart automatically on the next browser command" } else { [System.IO.File]::Move($staged, $Target) } + Write-Log "daemon will restart automatically on the next browser command" } finally { if ([System.IO.File]::Exists($staged)) { [System.IO.File]::Delete($staged) } diff --git a/scripts/install-windows.test.ps1 b/scripts/install-windows.test.ps1 index 9c9cd87..0555d23 100644 --- a/scripts/install-windows.test.ps1 +++ b/scripts/install-windows.test.ps1 @@ -143,11 +143,36 @@ try { & $target --version if ($LASTEXITCODE -ne 0) { throw "replacement is not executable" } + # Installing into a new directory must also stop a daemon from the old one. + $newTargetDir = Join-Path $root "new install" + [IO.Directory]::CreateDirectory($newTargetDir) | Out-Null + $newTarget = Join-Path $newTargetDir "bsk.exe" + $before = (Get-FileHash -LiteralPath $target).Hash + $daemon = Start-Process -FilePath $target -ArgumentList @('daemon', 'start', '--foreground', '--port', '0') -WindowStyle Hidden -PassThru + $deadline = [DateTime]::UtcNow.AddSeconds(15) + while (-not [IO.File]::Exists($infoPath)) { + if ($daemon.HasExited -or [DateTime]::UtcNow -gt $deadline) { throw "old-directory daemon failed to start" } + Start-Sleep -Milliseconds 50 + } + Install-Binary $source $newTarget + if (-not $daemon.WaitForExit(5000)) { throw "new-directory install left the old daemon running" } + Assert-Equal (Get-FileHash -LiteralPath $target).Hash $before + Assert-Equal (Get-FileHash -LiteralPath $newTarget).Hash (Get-FileHash -LiteralPath $source).Hash + & $newTarget --version + if ($LASTEXITCODE -ne 0) { throw "new-directory installation is not executable" } + if (@(Get-ChildItem -LiteralPath $newTargetDir -Filter "*.install-*").Count) { throw "new-directory staging files leaked" } + # Refuse replacement when daemon identity cannot be verified. [IO.File]::WriteAllText($infoPath, 'invalid daemon metadata') $before = (Get-FileHash -LiteralPath $target).Hash Assert-Fails { Install-Binary $source $target } Assert-Equal (Get-FileHash -LiteralPath $target).Hash $before + + # A failed stop must also prevent installation to a previously empty target. + $blockedTarget = Join-Path $newTargetDir "blocked.exe" + Assert-Fails { Install-Binary $source $blockedTarget } + if (Test-Path -LiteralPath $blockedTarget) { throw "failed stop created an installation" } + if (@(Get-ChildItem -LiteralPath $newTargetDir -Filter "*.install-*").Count) { throw "failed stop leaked staging files" } [IO.File]::Delete($infoPath) # A remaining lock must fail without truncation or staging debris.