Last active
March 9, 2026 15:01
-
-
Save Fronix/da7b33f92831087c1f4ef3ec4f111754 to your computer and use it in GitHub Desktop.
Meilisearch installer/updater for windows
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
| # Provided as is | |
| # Make sure to check the code! | |
| # Folder for Meilisearch should include config.toml, this is not created automatically atm | |
| # Script will require you to have the meilisearch binary exe on the filesystem | |
| # IT WILL NOT FETCH FROM GITHUB! | |
| # Now uses dumpless upgrade (--experimental-dumpless-upgrade) instead of dump/import | |
| function Show-Menu { | |
| Write-Host "=================================" | |
| Write-Host " Meilisearch Management Program" | |
| Write-Host "=================================" | |
| Write-Host "1: Install Meilisearch" | |
| Write-Host "2: Update Meilisearch" | |
| Write-Host "3: Exit" | |
| } | |
| # Define the source path for WinSW.exe | |
| $sourceWinSWPath = "D:\winsw.exe" | |
| # Define the default base URI for Meilisearch | |
| $defaultBaseUri = "http://localhost:7700/" | |
| # Define the base URI for the temporary upgrade instance | |
| $baseUriUpgrade = "http://localhost:7701/" | |
| function Check-WinSW { | |
| param ( | |
| [string]$meilisearchDir | |
| ) | |
| # Define the destination path for WinSW.exe in the Meilisearch directory | |
| $winswPath = "$meilisearchDir\winsw.exe" | |
| # Check if WinSW.exe exists in the destination directory | |
| if (-Not (Test-Path -Path $winswPath)) { | |
| # Try to copy from C:\winsw | |
| if (Test-Path -Path $sourceWinSWPath) { | |
| Copy-Item -Path $sourceWinSWPath -Destination $winswPath | |
| Write-Host "Copied WinSW.exe to $meilisearchDir" | |
| } else { | |
| Write-Host "WinSW.exe not found in $meilisearchDir or C:\winsw. Please ensure it is available before proceeding." | |
| Write-Host "Press any key to return to the main menu..." | |
| [void][System.Console]::ReadKey($true) | |
| return $false | |
| } | |
| } | |
| return $true | |
| } | |
| function WaitingForUser { | |
| param ( | |
| [switch]$ExitProgram | |
| ) | |
| Write-Host "Press any key to continue..." | |
| [void][System.Console]::ReadKey($true) | |
| if ($ExitProgram.IsPresent) { | |
| Write-Host "Exiting the program..." | |
| exit | |
| } else { | |
| return $false | |
| } | |
| } | |
| function Check-MeiliHealth { | |
| param ( | |
| [string]$meilisearchUrl | |
| ) | |
| $headers = @{ | |
| "Content-Type" = "application/json" | |
| } | |
| if (-not $meilisearchUrl.EndsWith("/")) { | |
| $meilisearchUrl = $meilisearchUrl + "/" | |
| } | |
| $healthUrl = $meilisearchUrl + "health" | |
| try { | |
| $healthResponse = Invoke-WebRequest -Uri $healthUrl -Method Get -Headers $headers | |
| if ($healthResponse.StatusCode -eq 200) { | |
| Write-Host "Meilisearch is healthy and running!" | |
| return $true | |
| } else { | |
| Write-Host "Meilisearch is not running or healthy. Status code: $($healthResponse.StatusCode)" | |
| return $false | |
| } | |
| } | |
| catch { | |
| return $false | |
| } | |
| } | |
| function Get-CurrentVersionDir { | |
| param ( | |
| [string]$meilisearchDir | |
| ) | |
| $xmlPath = "$meilisearchDir\meilisearch.xml" | |
| if (-Not (Test-Path -Path $xmlPath)) { | |
| Write-Host "Service config not found at $xmlPath" | |
| return $null | |
| } | |
| [xml]$config = Get-Content -Path $xmlPath | |
| $workingDir = $config.service.workingdirectory | |
| if ([string]::IsNullOrEmpty($workingDir)) { | |
| Write-Host "Could not determine current version directory from service config." | |
| return $null | |
| } | |
| return $workingDir | |
| } | |
| function Install-Meilisearch { | |
| # Step 1: Ask for the full path of the Meilisearch executable | |
| $meilisearchPath = Read-Host "Enter the full path of the Meilisearch executable (including the executable name)" | |
| # Step 2: Ask for the Meilisearch version | |
| $meilisearchVersion = Read-Host "Enter the Meilisearch version (e.g., v1.10.0)" | |
| # Step 3: Ask for the drive where Meilisearch should be installed | |
| $installDrive = Read-Host "Enter the drive letter where Meilisearch should be installed (e.g., E:)" | |
| # Step 4: Create the Meilisearch folder on the specified drive if it doesn't exist | |
| $meilisearchDir = "$installDrive\Meilisearch" | |
| if (-Not (Test-Path -Path $meilisearchDir)) { | |
| New-Item -Path $meilisearchDir -ItemType Directory | |
| Write-Host "Created directory: $meilisearchDir" | |
| } | |
| # Check if WinSW is installed before proceeding | |
| if (-Not (Check-WinSW -meilisearchDir $meilisearchDir)) { | |
| return | |
| } | |
| # Step 5: Create a version-specific folder within the Meilisearch directory | |
| $versionDir = "$meilisearchDir\$meilisearchVersion" | |
| if (-Not (Test-Path -Path $versionDir)) { | |
| New-Item -Path $versionDir -ItemType Directory | |
| Write-Host "Created directory: $versionDir" | |
| } | |
| # Step 6: Copy the executable to the version directory, rename it, and delete the original | |
| $destinationPath = "$versionDir\meilisearch.exe" | |
| Copy-Item -Path $meilisearchPath -Destination $destinationPath | |
| Write-Host "Copied Meilisearch executable to $destinationPath" | |
| Write-Host "Don't forget to delete the copied file $meilisearchPath" | |
| # Step 7: Setup or update the Meilisearch service using WinSW | |
| Setup-MeilisearchService $meilisearchDir $destinationPath $meilisearchVersion | |
| WaitingForUser | |
| } | |
| function Update-Meilisearch { | |
| # Step 1: Ask for the new Meilisearch version | |
| $newVersion = Read-Host "Enter the new Meilisearch version (e.g., v1.38.0)" | |
| # Step 2: Ask for the drive where Meilisearch is installed | |
| $installDrive = Read-Host "Enter the drive letter where Meilisearch is installed (e.g., E:)" | |
| # Step 3: Ask for the full path of the new Meilisearch executable | |
| $newMeilisearchPath = Read-Host "Enter the full path of the new Meilisearch executable (including the executable name)" | |
| # Step 4: Ask for the URL of the Meilisearch instance to verify it's running | |
| $meilisearchUrl = Read-Host "Enter the URL of the instance (leave empty for http://localhost:7700)" | |
| if ([string]::IsNullOrEmpty($meilisearchUrl)) { | |
| $meilisearchUrl = $defaultBaseUri | |
| } | |
| $meiliHealth = Check-MeiliHealth -meilisearchUrl $meilisearchUrl | |
| if (-Not $meiliHealth) { | |
| Write-Host "The old meilisearch instance is not running, start the service and try again." | |
| WaitingForUser -ExitProgram | |
| return | |
| } | |
| $meilisearchDir = "$installDrive\Meilisearch" | |
| # Check if WinSW is installed before proceeding | |
| if (-Not (Check-WinSW -meilisearchDir $meilisearchDir)) { | |
| return | |
| } | |
| # Step 5: Determine the old version's directory from the service config | |
| $oldVersionDir = Get-CurrentVersionDir -meilisearchDir $meilisearchDir | |
| if ($null -eq $oldVersionDir) { | |
| Write-Host "Could not determine the current version directory. Aborting." | |
| WaitingForUser -ExitProgram | |
| return | |
| } | |
| $oldDbPath = "$oldVersionDir\meili_db" | |
| if (-Not (Test-Path -Path $oldDbPath)) { | |
| Write-Host "Database not found at $oldDbPath. Aborting." | |
| WaitingForUser -ExitProgram | |
| return | |
| } | |
| Write-Host "Found existing database at: $oldDbPath" | |
| # Step 6: Create the new version directory and copy the executable | |
| $newVersionDir = "$meilisearchDir\$newVersion" | |
| if (-Not (Test-Path -Path $newVersionDir)) { | |
| New-Item -Path $newVersionDir -ItemType Directory | |
| Write-Host "Created directory: $newVersionDir" | |
| } | |
| $destinationPath = "$newVersionDir\meilisearch.exe" | |
| try { | |
| Copy-Item -Path $newMeilisearchPath -Destination $destinationPath | |
| Write-Host "Copied new Meilisearch executable to $destinationPath" | |
| } | |
| catch { | |
| Write-Host "Copy error: $_" | |
| WaitingForUser -ExitProgram | |
| return | |
| } | |
| # Step 7: Stop the old service | |
| $winswDir = $meilisearchDir | |
| Write-Host "Stopping the current Meilisearch service..." | |
| & "$winswDir\winsw.exe" stop "$winswDir\meilisearch.xml" | |
| Start-Sleep -Seconds 3 | |
| Write-Host "Service stopped." | |
| # Step 8: Copy the database to the new version directory | |
| $newDbPath = "$newVersionDir\meili_db" | |
| Write-Host "Copying database from $oldDbPath to $newDbPath (this may take a while for large databases)..." | |
| try { | |
| Copy-Item -Path $oldDbPath -Destination $newDbPath -Recurse | |
| Write-Host "Database copied successfully." | |
| } | |
| catch { | |
| Write-Host "Error copying database: $_" | |
| Write-Host "Restarting old service..." | |
| & "$winswDir\winsw.exe" start "$winswDir\meilisearch.xml" | |
| WaitingForUser -ExitProgram | |
| return | |
| } | |
| # Step 9: Run the new binary with --experimental-dumpless-upgrade to migrate the database | |
| Write-Host "Starting dumpless upgrade with new binary..." | |
| try { | |
| $meilisearchProcess = Start-Process -FilePath "meilisearch.exe" ` | |
| -WorkingDirectory $newVersionDir ` | |
| -ArgumentList "--experimental-dumpless-upgrade", "--db-path", "./meili_db", "--http-addr", "localhost:7701", "--config-file-path", "../config.toml" ` | |
| -PassThru | |
| Write-Host "Dumpless upgrade started (Process ID: $($meilisearchProcess.Id))" | |
| Write-Host "Waiting for the upgrade to complete..." | |
| Start-Sleep -Seconds 10 | |
| # Step 10: Wait for the upgrade instance to become healthy | |
| $maxAttempts = 60 | |
| $attempt = 0 | |
| $upgradeHealthy = $false | |
| while ($attempt -lt $maxAttempts) { | |
| try { | |
| $healthResponse = Invoke-WebRequest -Uri ($baseUriUpgrade + "health") -Method Get -Headers @{"Content-Type" = "application/json"} | |
| if ($healthResponse.StatusCode -eq 200) { | |
| Write-Host "Dumpless upgrade completed successfully!" | |
| $upgradeHealthy = $true | |
| break | |
| } | |
| } | |
| catch { | |
| Write-Host "Still upgrading..." | |
| } | |
| $attempt++ | |
| Start-Sleep -Seconds 5 | |
| } | |
| # Step 11: Stop the temporary upgrade process | |
| if (-Not $meilisearchProcess.HasExited) { | |
| Stop-Process -Id $meilisearchProcess.Id -Force | |
| Write-Host "Stopped temporary upgrade instance." | |
| } | |
| if (-Not $upgradeHealthy) { | |
| Write-Host "Dumpless upgrade did not complete within the expected time. Check logs in $newVersionDir." | |
| Write-Host "Restarting old service..." | |
| & "$winswDir\winsw.exe" start "$winswDir\meilisearch.xml" | |
| WaitingForUser -ExitProgram | |
| return | |
| } | |
| } | |
| catch { | |
| Write-Host "Error during dumpless upgrade: $_" | |
| if (-Not $meilisearchProcess.HasExited) { | |
| Stop-Process -Id $meilisearchProcess.Id -Force | |
| } | |
| Write-Host "Restarting old service..." | |
| & "$winswDir\winsw.exe" start "$winswDir\meilisearch.xml" | |
| WaitingForUser -ExitProgram | |
| return | |
| } | |
| Write-Host "Don't forget to delete the copied file $newMeilisearchPath" | |
| Read-Host "Upgrade is done, ready to update the service. Press any key to continue..." | |
| # Step 12: Update the Meilisearch service to point to the new version | |
| Setup-MeilisearchService $meilisearchDir $destinationPath $newVersion | |
| # Old version directory kept for rollback purposes (database copy preserved) | |
| Write-Host "Old version directory kept at $oldVersionDir for rollback purposes." | |
| WaitingForUser | |
| } | |
| # --- Dump functions kept as fallback utilities --- | |
| function New-MeiliearchDump { | |
| param ( | |
| [string]$apiKey, | |
| [string]$baseUri | |
| ) | |
| $headers = @{ | |
| "Authorization" = "Bearer $apiKey" | |
| "Content-Type" = "application/json" | |
| } | |
| if (-not $baseUri.EndsWith("/")) { | |
| $baseUri = $baseUri + "/" | |
| } | |
| $dumpsUri = $baseUri + "dumps" | |
| $meiliHealth = Check-MeiliHealth -meilisearchUrl $baseUri | |
| if (-Not $meiliHealth) { | |
| Write-Host "The old meilisearch instance is not running, start the service and try again." | |
| WaitingForUser -ExitProgram | |
| return | |
| } | |
| try { | |
| $dumpResponse = Invoke-RestMethod -Uri $dumpsUri -Method Post -Headers $headers | |
| $taskId = $dumpResponse.taskUid | |
| Write-Host "Dump creation initiated: $($dumpResponse.dumpUid)" | |
| while ($true) { | |
| $tasksUri = $baseUri + "tasks/$taskId" | |
| $statusResponse = Invoke-RestMethod -Uri $tasksUri -Method Get -Headers $headers | |
| if ($statusResponse.status -eq "succeeded") { | |
| Write-Host "Dump is ready!" | |
| break | |
| } elseif ($statusResponse.status -eq "failed" -or $statusResponse.status -eq "canceled") { | |
| Write-Host "Dump creation failed!" | |
| return | |
| } | |
| Write-Host "Dump is not ready yet, waiting..." | |
| Start-Sleep -Seconds 5 | |
| } | |
| } | |
| catch { | |
| Write-Host "Error creating dump: $_" | |
| WaitingForUser -ExitProgram | |
| } | |
| } | |
| function Import-MeilisearchDump { | |
| param ( | |
| [string]$dumpDir, | |
| [string]$newMeilisearchPath | |
| ) | |
| $headers = @{ | |
| "Content-Type" = "application/json" | |
| } | |
| $latestDump = Get-ChildItem -Path $dumpDir | Sort-Object LastWriteTime -Descending | Select-Object -First 1 | |
| try { | |
| $baseUri = $baseUriUpgrade | |
| $healthUrl = $baseUri + "health" | |
| Write-Host "Starting dump import using the latest dump ($($latestDump.FullName))" | |
| $meilisearchProcess = Start-Process -FilePath "meilisearch.exe" -WorkingDirectory $newMeilisearchPath -ArgumentList "--db-path", "./meili_db", "--import-dump", $latestDump.FullName, "--http-addr", "localhost:7701" -PassThru | |
| Write-Host "Dump import started.... (Process ID: $($meilisearchProcess.Id))" | |
| Write-Host "Waiting 10 seconds for the instance to start..." | |
| Start-Sleep -Seconds 10 | |
| Write-Host "Waiting for dump import to be processed..." | |
| while ($true) { | |
| try { | |
| $healthResponse = Invoke-WebRequest -Uri $healthUrl -Method Get -Headers $headers | |
| if ($healthResponse.StatusCode -eq 200) { | |
| Write-Host "Dump import completed successfully! Stopping import instance..." | |
| Stop-Process -Id $meilisearchProcess.Id -Force | |
| break | |
| } | |
| } | |
| catch { | |
| Write-Host "Connection failed, Still processing..." | |
| } | |
| Start-Sleep -Seconds 5 | |
| Write-Host "Still processing..." | |
| } | |
| } | |
| catch { | |
| Write-Host "Error importing dump: $_" | |
| WaitingForUser -ExitProgram | |
| } | |
| } | |
| # --- End dump functions --- | |
| function Setup-MeilisearchService { | |
| param ( | |
| [string]$meilisearchDir, | |
| [string]$executablePath, | |
| [string]$meilisearchVersion | |
| ) | |
| $serviceName = "MeiliSearch" | |
| $serviceDescription = "MeiliSearch Service - Version $meilisearchVersion" | |
| $meilisearchConfigPath = "$meilisearchDir\config.toml" | |
| $versionDir = "$meilisearchDir\$meilisearchVersion" | |
| $winswConfig = @" | |
| <service> | |
| <id>$serviceName</id> | |
| <name>MeiliSearch</name> | |
| <description>$serviceDescription</description> | |
| <executable>$executablePath</executable> | |
| <logpath>%BASE%\logs</logpath> | |
| <arguments>"--config-file-path" ../config.toml "--db-path" ./meili_db</arguments> | |
| <log mode="roll-by-time"> | |
| <pattern>yyyyMMdd</pattern> | |
| </log> | |
| <startmode>automatic</startmode> | |
| <priority>high</priority> | |
| <autoRefresh>false</autoRefresh> | |
| <workingdirectory>$versionDir</workingdirectory> | |
| </service> | |
| "@ | |
| $winswConfigPath = "$meilisearchDir\meilisearch.xml" | |
| $winswConfig | Out-File -FilePath $winswConfigPath -Encoding utf8 | |
| $winswDir = $meilisearchDir | |
| Write-Host "Created or updated WinSW service configuration file at $winswConfigPath" | |
| if (Get-Service -Name $serviceName -ErrorAction SilentlyContinue) { | |
| & "$winswDir\winsw.exe" stop "$winswDir\meilisearch.xml" | |
| Write-Host "Stopped existing Meilisearch service" | |
| } | |
| & "$winswDir\winsw.exe" uninstall "$winswDir\meilisearch.xml" | |
| Write-Host "Uninstalled old Meilisearch service using WinSW" | |
| & "$winswDir\winsw.exe" install "$winswDir\meilisearch.xml" | |
| Write-Host "Installed new Meilisearch service using WinSW" | |
| & "$winswDir\winsw.exe" start "$winswDir\meilisearch.xml" | |
| Write-Host "Started Meilisearch service" | |
| } | |
| function Main { | |
| while ($true) { | |
| Show-Menu | |
| $choice = Read-Host "Select an option" | |
| switch ($choice) { | |
| "1" { Install-Meilisearch } | |
| "2" { Update-Meilisearch } | |
| "3" { Write-Host "Exiting..."; exit } | |
| default { Write-Host "Invalid option. Please try again." } | |
| } | |
| } | |
| } | |
| # Start the program | |
| Main |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment