Repository navigation
script to install/update kurrent db #831
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,221 @@ | ||
| #requires -Version 5.1 | ||
| #requires -RunAsAdministrator | ||
| <# | ||
| .SYNOPSIS | ||
| Updates KurrentDB through Chocolatey and deploys it to a fixed service directory. | ||
| .DESCRIPTION | ||
| Run from an elevated 64-bit PowerShell window. Downloads the latest stable | ||
| package (no --pre). Archives the old service directory outside Chocolatey's lib | ||
| folder before uninstalling the registered Chocolatey package and reinstalling it. | ||
| Existing service account/settings are preserved. Requires Chocolatey 2.x or newer. | ||
| Archives contain the service directory only, NOT an external database backup. | ||
| Keep database/index paths outside the service directory in your YAML config. | ||
| Automatic binary restoration is attempted only BEFORE the new server is started. | ||
| After startup has been attempted, manual recovery is required: an older binary | ||
| may not be compatible with data touched by the newer server. | ||
| .PARAMETER Source | ||
| Optional Chocolatey feed. Omit to use your configured Chocolatey sources. | ||
| .PARAMETER Version | ||
| Optional exact stable package version, for a controlled update. | ||
| .EXAMPLE | ||
| .\Update-KurrentDB.ps1 | ||
| .EXAMPLE | ||
| .\Update-KurrentDB.ps1 -Version 26.1.2 | ||
| #> | ||
| [CmdletBinding()] | ||
| param( | ||
| [string]$ServiceName = 'KurrentDB', | ||
| [string]$PackageRoot = 'C:\ProgramData\chocolatey\lib\kurrentdb', | ||
| [string]$ConfigPath = 'C:\home\txnproc\config\kurrentdb-config.yaml', | ||
| [string]$ArchiveRoot = 'C:\ProgramData\KurrentDB-Archives', | ||
| [string]$Source, | ||
| [string]$Version, | ||
| [ValidateRange(10, 600)][int]$ServiceTimeoutSeconds = 120 | ||
| ) | ||
| Set-StrictMode -Version Latest | ||
| $ErrorActionPreference = 'Stop' | ||
| # Native failures are checked explicitly, including in PowerShell 7. | ||
| $PSNativeCommandUseErrorActionPreference = $false | ||
| if (-not [Environment]::Is64BitProcess) { throw 'Run this script in 64-bit PowerShell.' } | ||
| if ($Version -and $Version -notmatch '^\d+\.\d+\.\d+(\.\d+)?$') { | ||
| throw 'Version must be an exact stable numeric version, for example 26.1.2.' | ||
| } | ||
| $choco = (Get-Command choco.exe -ErrorAction Stop).Source | ||
| $PackageRoot = [IO.Path]::GetFullPath($PackageRoot).TrimEnd('\') | ||
| $ArchiveRoot = [IO.Path]::GetFullPath($ArchiveRoot).TrimEnd('\') | ||
| $serviceDirectory = Join-Path $PackageRoot 'kurrentservice' | ||
| $executable = Join-Path $serviceDirectory 'KurrentDB.exe' | ||
| $binaryPath = '"{0}" --config="{1}"' -f $executable, $ConfigPath | ||
| if (-not (Test-Path -LiteralPath $ConfigPath -PathType Leaf)) { throw "Config not found: $ConfigPath" } | ||
| if ($ArchiveRoot.Equals($PackageRoot, [StringComparison]::OrdinalIgnoreCase) -or | ||
| $ArchiveRoot.StartsWith($PackageRoot + '\', [StringComparison]::OrdinalIgnoreCase)) { | ||
| throw 'ArchiveRoot must be outside the Chocolatey package directory.' | ||
| } | ||
| function Stop-KurrentService { | ||
| [CmdletBinding(SupportsShouldProcess)] | ||
| param() | ||
|
|
||
| $current = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue | ||
| if ($null -ne $current -and $current.Status -ne 'Stopped' -and | ||
| $PSCmdlet.ShouldProcess($ServiceName, 'Stop service')) { | ||
| Stop-Service -Name $ServiceName -ErrorAction Stop | ||
| $current.WaitForStatus([ServiceProcess.ServiceControllerStatus]::Stopped, | ||
| [TimeSpan]::FromSeconds($ServiceTimeoutSeconds)) | ||
| } | ||
| } | ||
| function Start-KurrentService { | ||
| [CmdletBinding(SupportsShouldProcess)] | ||
| param() | ||
|
|
||
| if (-not $PSCmdlet.ShouldProcess($ServiceName, 'Start service')) { return } | ||
|
|
||
| Start-Service -Name $ServiceName -ErrorAction Stop | ||
| $current = Get-Service -Name $ServiceName | ||
| $current.WaitForStatus([ServiceProcess.ServiceControllerStatus]::Running, | ||
| [TimeSpan]::FromSeconds($ServiceTimeoutSeconds)) | ||
| # Catch immediate process exits; this is a service check, not database readiness. | ||
| Start-Sleep -Seconds 5 | ||
| $current.Refresh() | ||
| if ($current.Status -ne 'Running') { throw 'KurrentDB exited shortly after starting. Check its logs.' } | ||
| } | ||
| function Copy-DirectoryContents([string]$From, [string]$To) { | ||
| New-Item -ItemType Directory -Path $To -Force | Out-Null | ||
| Get-ChildItem -LiteralPath $From -Force | ForEach-Object { | ||
| Copy-Item -LiteralPath $_.FullName -Destination $To -Recurse -Force | ||
| } | ||
| } | ||
| $mutex = New-Object Threading.Mutex($false, 'Global\KurrentDB-Chocolatey-Update') | ||
| $locked = $false | ||
| $archive = $null | ||
| $archiveComplete = $false | ||
| $staging = $null | ||
| $wasRunning = $false | ||
| $startupAttempted = $false | ||
| $replacementStarted = $false | ||
| $createdService = $false | ||
| $stoppedByScript = $false | ||
| try { | ||
| try { $locked = $mutex.WaitOne(0) } catch [Threading.AbandonedMutexException] { $locked = $true } | ||
| if (-not $locked) { throw 'Another KurrentDB update is already running.' } | ||
| $service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue | ||
| if ($null -ne $service) { | ||
| $info = Get-CimInstance Win32_Service | Where-Object { $_.Name -eq $ServiceName } | ||
| # Refuse to restart a different executable/config by accident. | ||
| $normalized = $info.PathName.Replace('"', '').Trim() | ||
| $expected = ('{0} --config={1}' -f $executable, $ConfigPath) | ||
| if (-not $normalized.Equals($expected, [StringComparison]::OrdinalIgnoreCase)) { | ||
| throw "Existing service has a different command line: $($info.PathName). Expected: $binaryPath" | ||
| } | ||
| if ($info.StartMode -eq 'Disabled') { throw 'The existing service is disabled. Enable it before updating.' } | ||
| if (-not (Test-Path -LiteralPath $executable -PathType Leaf)) { throw "Existing service executable not found: $executable" } | ||
| $wasRunning = $service.Status -eq 'Running' | ||
| Write-Output "Stopping $ServiceName..." | ||
| Stop-KurrentService | ||
| $stoppedByScript = $true | ||
| } | ||
| $runId = (Get-Date -Format 'yyyyMMdd-HHmmss') + '-' + [Guid]::NewGuid().ToString('N').Substring(0,8) | ||
| New-Item -ItemType Directory -Path $ArchiveRoot -Force | Out-Null | ||
| if (Test-Path -LiteralPath $serviceDirectory -PathType Container) { | ||
| $archive = Join-Path $ArchiveRoot "kurrentservice-$runId" | ||
| Write-Output "Archiving existing directory to $archive" | ||
| Copy-DirectoryContents $serviceDirectory $archive | ||
| # Only mark the archive usable after the entire copy succeeds. | ||
| $archiveComplete = $true | ||
| } else { $archiveComplete = $false } | ||
| # Chocolatey 2.x lists locally registered packages by default. | ||
| # Capture output so we can distinguish an installed package from leftover files. | ||
| Write-Output 'Checking the Chocolatey installed-package list...' | ||
| $packageList = @(& $choco list kurrentdb --exact --limit-output) | ||
| $listExitCode = $LASTEXITCODE | ||
| if ($listExitCode -notin @(0, 2)) { | ||
| throw "Cannot query Chocolatey installed packages (exit code $listExitCode)." | ||
| } | ||
| $registeredPackage = @($packageList | Where-Object { $_ -match '^kurrentdb\|' }) | ||
| if ($registeredPackage.Count -gt 0) { | ||
| Write-Output 'Uninstalling the currently registered KurrentDB Chocolatey package...' | ||
| & $choco uninstall kurrentdb --yes --no-progress --limit-output | ||
| $uninstallExitCode = $LASTEXITCODE | ||
| if ($uninstallExitCode -notin @(0, 1605, 1614, 1641, 3010)) { | ||
| throw "Chocolatey uninstall failed with exit code $uninstallExitCode." | ||
| } | ||
| if ($uninstallExitCode -in @(1641, 3010)) { | ||
| throw 'Chocolatey uninstall reports a reboot is needed. Reboot and rerun this script.' | ||
| } | ||
| # Do not proceed with an install that could silently skip an existing registration. | ||
| $remainingPackages = @(& $choco list kurrentdb --exact --limit-output) | ||
| $verificationExitCode = $LASTEXITCODE | ||
| if ($verificationExitCode -notin @(0, 2)) { | ||
| throw "Cannot verify Chocolatey uninstall (exit code $verificationExitCode)." | ||
| } | ||
| if (@($remainingPackages | Where-Object { $_ -match '^kurrentdb\|' }).Count -gt 0) { | ||
| throw 'KurrentDB is still registered in Chocolatey after uninstall. Installation aborted.' | ||
| } | ||
| } else { | ||
| Write-Output 'KurrentDB is not registered in Chocolatey; proceeding with installation.' | ||
| } | ||
| if ($Version) { Write-Output "Installing KurrentDB package version $Version..." } | ||
| else { Write-Output 'Installing the latest stable KurrentDB package...' } | ||
| $arguments = @('install', 'kurrentdb', '--yes', '--no-progress', '--limit-output') | ||
| if ($Source) { $arguments += "--source=$Source" } | ||
| if ($Version) { $arguments += "--version=$Version" } | ||
| & $choco @arguments | ||
| $chocoExitCode = $LASTEXITCODE | ||
| if ($chocoExitCode -notin @(0, 2, 1641, 3010)) { throw "Chocolatey failed with exit code $chocoExitCode. See Chocolatey's log." } | ||
| if ($chocoExitCode -in @(1641, 3010)) { throw 'Chocolatey reports a reboot is needed. Reboot and rerun this script before deploying.' } | ||
| $nuspecPath = Join-Path $PackageRoot 'kurrentdb.nuspec' | ||
| if (-not (Test-Path -LiteralPath $nuspecPath -PathType Leaf)) { throw "Package metadata missing: $nuspecPath" } | ||
| [xml]$nuspec = Get-Content -LiteralPath $nuspecPath -Raw | ||
| $versionNode = $nuspec.SelectSingleNode("/*[local-name()='package']/*[local-name()='metadata']/*[local-name()='version']") | ||
| if ($null -eq $versionNode) { throw 'Cannot read installed package version.' } | ||
| $installedVersion = $versionNode.InnerText.Trim() | ||
| if ($installedVersion -notmatch '^\d+\.\d+\.\d+(\.\d+)?$') { throw "Refusing non-stable package version: $installedVersion" } | ||
| if ($Version -and $installedVersion -ne $Version) { throw "Requested $Version but installed package is $installedVersion. Check package pins." } | ||
| $sourceDirectory = Join-Path $PackageRoot "kurrentdb-$installedVersion-windows.x64" | ||
| if (-not (Test-Path -LiteralPath (Join-Path $sourceDirectory 'KurrentDB.exe') -PathType Leaf)) { | ||
| throw "Expected version directory missing or incomplete: $sourceDirectory" | ||
| } | ||
| $staging = Join-Path $ArchiveRoot "staging-$runId" | ||
| Copy-DirectoryContents $sourceDirectory $staging | ||
| Write-Output "Deploying KurrentDB $installedVersion to $serviceDirectory" | ||
| Stop-KurrentService | ||
| $replacementStarted = $true | ||
| if (Test-Path -LiteralPath $serviceDirectory) { Remove-Item -LiteralPath $serviceDirectory -Recurse -Force } | ||
| Copy-DirectoryContents $staging $serviceDirectory | ||
|
StuartFerguson marked this conversation as resolved.
Resolved
|
||
| if ($null -eq $service) { | ||
| Write-Output "Creating service $ServiceName..." | ||
| New-Service -Name $ServiceName -DisplayName $ServiceName -BinaryPathName $binaryPath -StartupType Automatic | Out-Null | ||
| $createdService = $true | ||
| } | ||
| $startupAttempted = $true | ||
| Write-Output "Starting $ServiceName..." | ||
| Start-KurrentService | ||
| Write-Output "KurrentDB $installedVersion deployed. Service is running." | ||
| if ($archive) { Write-Output "Previous installation archive: $archive" } | ||
| Write-Output 'Service status was verified; database readiness was not checked.' | ||
| } catch { | ||
| $failure = $_ | ||
| if ($locked) { | ||
| try { | ||
| if ($startupAttempted) { | ||
| Stop-KurrentService | ||
| Write-Warning "Startup was attempted. No automatic downgrade was performed. Review KurrentDB logs and database compatibility before restoring binaries from $archive." | ||
| } elseif ($archive -and $archiveComplete) { | ||
| Stop-KurrentService | ||
| if (Test-Path -LiteralPath $serviceDirectory) { Remove-Item -LiteralPath $serviceDirectory -Recurse -Force } | ||
| Copy-DirectoryContents $archive $serviceDirectory | ||
| if ($wasRunning) { Start-KurrentService } | ||
| Write-Warning 'Previous service directory restored. Chocolatey package registration may differ from the restored binaries; rerun after resolving the failure.' | ||
| } elseif ($stoppedByScript -and $wasRunning -and -not $replacementStarted) { | ||
| Start-KurrentService | ||
| } | ||
| } catch { Write-Warning "Recovery also failed: $($_.Exception.Message). Archive: $archive" } | ||
| if ($createdService) { Write-Warning 'The newly created service remains registered for troubleshooting.' } | ||
| } | ||
| throw $failure | ||
| } finally { | ||
| if ($staging -and (Test-Path -LiteralPath $staging)) { | ||
| try { Remove-Item -LiteralPath $staging -Recurse -Force } catch { Write-Warning "Staging cleanup failed: $staging" } | ||
| } | ||
| if ($locked) { $mutex.ReleaseMutex() } | ||
| $mutex.Dispose() | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.