aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJJ Liebig <jonathan.liebig@gmail.com>2026-08-08 21:59:32 +0200
committerGitHub <noreply@github.com>2026-08-08 22:59:32 +0300
commit3a76fea2d858a7a5ef58615f1cc9aabaa30b80ee (patch)
tree8b19f9a758b594e79fa31f49a1242e52fdef06c6
parent10974c822d607f03e20e9741ec027910f0c1f93a (diff)
fix: make windows installer swaps atomic (#2530)
refs #2356
-rw-r--r--scripts/windows_install_conpty_package_test.ps150
-rw-r--r--website/install.ps17
2 files changed, 53 insertions, 4 deletions
diff --git a/scripts/windows_install_conpty_package_test.ps1 b/scripts/windows_install_conpty_package_test.ps1
index 232e61ac..0da92989 100644
--- a/scripts/windows_install_conpty_package_test.ps1
+++ b/scripts/windows_install_conpty_package_test.ps1
@@ -133,7 +133,8 @@ try {
}
}
- $releaseDir = Get-ChildItem -LiteralPath (Join-Path $herdrHome "packages\standalone\releases") -Directory |
+ $releasesDir = Join-Path $herdrHome "packages\standalone\releases"
+ $releaseDir = Get-ChildItem -LiteralPath $releasesDir -Directory |
Where-Object { -not $_.Name.StartsWith(".staging.") } |
Select-Object -First 1
if ($null -eq $releaseDir) {
@@ -164,6 +165,53 @@ try {
}
$manifest | Out-File -LiteralPath $manifestPath -Encoding utf8
+ $stagedConpty = Join-Path $releasesDir ".staging.$($releaseDir.Name).$PID\conpty\conpty.dll"
+ $lockState = @{ Handle = $null }
+ $lockStagedFile = {
+ if ($null -eq $lockState.Handle) {
+ $lockState.Handle = [System.IO.File]::Open(
+ $stagedConpty,
+ [System.IO.FileMode]::Open,
+ [System.IO.FileAccess]::Read,
+ [System.IO.FileShare]::Read
+ )
+ }
+ }.GetNewClosure()
+ $swapBreakpoint = Set-PSBreakpoint -Script $installerPath -Variable "backupDir" -Mode Write -Action $lockStagedFile
+ try {
+ $swapFailed = $false
+ try {
+ & "$PSScriptRoot\..\website\install.ps1" `
+ -ManifestUrl $manifestUrl `
+ -InstallDir $installDir `
+ -ExpectedBuildId "installer-test"
+ } catch {
+ $swapFailed = $true
+ }
+ if ($null -eq $lockState.Handle) {
+ throw "installer did not acquire the staged file handle before the swap"
+ }
+ if (-not $swapFailed) {
+ throw "installer unexpectedly activated a release with a locked staged file"
+ }
+ if (-not (Test-Path -LiteralPath (Join-Path $releaseDir.FullName "herdr.exe") -PathType Leaf)) {
+ throw "failed activation did not restore the prior release"
+ }
+ if (@(Get-ChildItem -LiteralPath $releasesDir -Force -Directory -Filter ".backup.$($releaseDir.Name).*").Count -ne 0) {
+ throw "failed activation stranded a release backup"
+ }
+ foreach ($junction in @($installDir, (Join-Path $herdrHome "packages\standalone\current"))) {
+ if (-not (Test-Path -LiteralPath (Join-Path $junction "herdr.exe") -PathType Leaf)) {
+ throw "failed activation left an invalid installer junction at $junction"
+ }
+ }
+ } finally {
+ Remove-PSBreakpoint -Breakpoint $swapBreakpoint
+ if ($null -ne $lockState.Handle) {
+ $lockState.Handle.Dispose()
+ }
+ }
+
& "$PSScriptRoot\..\website\install.ps1" `
-ManifestUrl $manifestUrl `
-InstallDir $installDir `
diff --git a/website/install.ps1 b/website/install.ps1
index b7ea80aa..d6367312 100644
--- a/website/install.ps1
+++ b/website/install.ps1
@@ -632,14 +632,15 @@ try {
$backupDir = $null
if (Test-Path -LiteralPath $releaseDir) {
$backupDir = Join-Path $releasesDir ".backup.$releaseName.$([System.Guid]::NewGuid().ToString('N'))"
- Move-Item -LiteralPath $releaseDir -Destination $backupDir
+ [System.IO.Directory]::Move($releaseDir, $backupDir)
}
try {
- Move-Item -LiteralPath $stagingDir -Destination $releaseDir
+ [System.IO.Directory]::Move($stagingDir, $releaseDir)
} catch {
if ($null -ne $backupDir -and -not (Test-Path -LiteralPath $releaseDir)) {
- Move-Item -LiteralPath $backupDir -Destination $releaseDir
+ [System.IO.Directory]::Move($backupDir, $releaseDir)
}
+ Write-WarningStep "Windows could not activate the downloaded release. Another process may have a package file open, such as antivirus or indexing. No incomplete release was activated. Run herdr update again."
throw
}
if ($null -ne $backupDir) {