From aea316f1e908b5d12243f30aca9c543fdccba516 Mon Sep 17 00:00:00 2001 From: Daniel Hiltgen Date: Mon, 9 Feb 2026 15:28:11 -0800 Subject: [PATCH] win: add curl-style install script (#14178) This adds a new powershell install script suitable for running via irm https://ollama.com/install.ps1 | iex If you download the script and run '-?' it reports basic usage information, as well as usage examples for common customization options. The script is signed as part of the release process to ensure it can run on a typically configured Windows system. This does not include doc updates - we can merge those after a release ships to avoid user confusion. --- .github/workflows/release.yaml | 6 +- scripts/build_windows.ps1 | 10 ++ scripts/install.ps1 | 271 +++++++++++++++++++++++++++++++++ scripts/install.sh | 10 +- 4 files changed, 289 insertions(+), 8 deletions(-) create mode 100644 scripts/install.ps1 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 6211d265f..6d9596807 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -337,6 +337,7 @@ jobs: name: bundles-windows path: | dist/*.zip + dist/*.ps1 dist/OllamaSetup.exe linux-build: @@ -514,6 +515,9 @@ jobs: - name: Log dist contents run: | ls -l dist/ + - name: Copy install scripts to dist + run: | + cp scripts/install.sh dist/install.sh - name: Generate checksum file run: find . -type f -not -name 'sha256sum.txt' | xargs sha256sum | tee sha256sum.txt working-directory: dist @@ -536,7 +540,7 @@ jobs: - name: Upload release artifacts run: | pids=() - for payload in dist/*.txt dist/*.zip dist/*.tgz dist/*.tar.zst dist/*.exe dist/*.dmg ; do + for payload in dist/*.txt dist/*.zip dist/*.tgz dist/*.tar.zst dist/*.exe dist/*.dmg dist/*.ps1 dist/*.sh ; do echo "Uploading $payload" gh release upload ${GITHUB_REF_NAME} $payload --clobber & pids[$!]=$! diff --git a/scripts/build_windows.ps1 b/scripts/build_windows.ps1 index 0ca00fa73..21e6f3be0 100644 --- a/scripts/build_windows.ps1 +++ b/scripts/build_windows.ps1 @@ -302,12 +302,22 @@ function deps { } function sign { + # Copy install.ps1 to dist for release packaging + write-host "Copying install.ps1 to dist" + Copy-Item -Path "${script:SRC_DIR}\scripts\install.ps1" -Destination "${script:SRC_DIR}\dist\install.ps1" + if ("${env:KEY_CONTAINER}") { write-host "Signing Ollama executables, scripts and libraries" & "${script:SignTool}" sign /v /fd sha256 /t http://timestamp.digicert.com /f "${script:OLLAMA_CERT}" ` /csp "Google Cloud KMS Provider" /kc ${env:KEY_CONTAINER} ` $(get-childitem -path "${script:SRC_DIR}\dist\windows-*" -r -include @('*.exe', '*.dll')) if ($LASTEXITCODE -ne 0) { exit($LASTEXITCODE)} + + write-host "Signing install.ps1" + & "${script:SignTool}" sign /v /fd sha256 /t http://timestamp.digicert.com /f "${script:OLLAMA_CERT}" ` + /csp "Google Cloud KMS Provider" /kc ${env:KEY_CONTAINER} ` + "${script:SRC_DIR}\dist\install.ps1" + if ($LASTEXITCODE -ne 0) { exit($LASTEXITCODE)} } else { write-host "Signing not enabled" } diff --git a/scripts/install.ps1 b/scripts/install.ps1 new file mode 100644 index 000000000..0d9592502 --- /dev/null +++ b/scripts/install.ps1 @@ -0,0 +1,271 @@ +<# +.SYNOPSIS + Install, upgrade, or uninstall Ollama on Windows. + +.DESCRIPTION + Downloads and installs Ollama. + + Quick install: + + irm https://ollama.com/install.ps1 | iex + + Specific version: + + $env:OLLAMA_VERSION="0.5.7"; irm https://ollama.com/install.ps1 | iex + + Custom install directory: + + $env:OLLAMA_INSTALL_DIR="D:\Ollama"; irm https://ollama.com/install.ps1 | iex + + Uninstall: + + $env:OLLAMA_UNINSTALL=1; irm https://ollama.com/install.ps1 | iex + + Environment variables: + + OLLAMA_VERSION Target version (default: latest stable) + OLLAMA_INSTALL_DIR Custom install directory + OLLAMA_UNINSTALL Set to 1 to uninstall Ollama + OLLAMA_DEBUG Enable verbose output + +.EXAMPLE + irm https://ollama.com/install.ps1 | iex + +.EXAMPLE + $env:OLLAMA_VERSION = "0.5.7"; irm https://ollama.com/install.ps1 | iex + +.LINK + https://ollama.com +#> + +$ErrorActionPreference = "Stop" +$ProgressPreference = "SilentlyContinue" + +# -------------------------------------------------------------------------- +# Configuration from environment variables +# -------------------------------------------------------------------------- + +$Version = if ($env:OLLAMA_VERSION) { $env:OLLAMA_VERSION } else { "" } +$InstallDir = if ($env:OLLAMA_INSTALL_DIR) { $env:OLLAMA_INSTALL_DIR } else { "" } +$Uninstall = $env:OLLAMA_UNINSTALL -eq "1" +$DebugInstall = [bool]$env:OLLAMA_DEBUG + +# -------------------------------------------------------------------------- +# Constants +# -------------------------------------------------------------------------- + +# OLLAMA_DOWNLOAD_URL for developer testing only +$DownloadBaseURL = if ($env:OLLAMA_DOWNLOAD_URL) { $env:OLLAMA_DOWNLOAD_URL.TrimEnd('/') } else { "https://ollama.com/download" } +$InnoSetupUninstallGuid = "{44E83376-CE68-45EB-8FC1-393500EB558C}_is1" + +# -------------------------------------------------------------------------- +# Helpers +# -------------------------------------------------------------------------- + +function Write-Status { + param([string]$Message) + if ($DebugInstall) { Write-Host $Message } +} + +function Write-Step { + param([string]$Message) + if ($DebugInstall) { Write-Host ">>> $Message" -ForegroundColor Cyan } +} + +function Test-Signature { + param([string]$FilePath) + + $sig = Get-AuthenticodeSignature -FilePath $FilePath + if ($sig.Status -ne "Valid") { + Write-Status " Signature status: $($sig.Status)" + return $false + } + + # Verify it's signed by Ollama Inc. (check exact organization name) + # Anchor with comma/boundary to prevent "O=Not Ollama Inc." from matching + $subject = $sig.SignerCertificate.Subject + if ($subject -notmatch "(^|, )O=Ollama Inc\.(,|$)") { + Write-Status " Unexpected signer: $subject" + return $false + } + + Write-Status " Signature valid: $subject" + return $true +} + +function Find-InnoSetupInstall { + # Check both HKCU (per-user) and HKLM (per-machine) locations + $possibleKeys = @( + "HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\$InnoSetupUninstallGuid", + "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\$InnoSetupUninstallGuid", + "HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\$InnoSetupUninstallGuid" + ) + + foreach ($key in $possibleKeys) { + if (Test-Path $key) { + Write-Status " Found install at: $key" + return $key + } + } + return $null +} + +function Update-SessionPath { + # Update PATH in current session so 'ollama' works immediately + if ($InstallDir) { + $ollamaDir = $InstallDir + } else { + $ollamaDir = Join-Path $env:LOCALAPPDATA "Programs\Ollama" + } + + # Add to PATH if not already present + if (Test-Path $ollamaDir) { + $currentPath = $env:PATH -split ';' + if ($ollamaDir -notin $currentPath) { + $env:PATH = "$ollamaDir;$env:PATH" + Write-Status " Added $ollamaDir to session PATH" + } + } +} + +function Invoke-Download { + param( + [string]$Url, + [string]$OutFile + ) + + Write-Status " Downloading: $Url" + try { + Invoke-WebRequest -Uri $Url -OutFile $OutFile -UseBasicParsing + $size = (Get-Item $OutFile).Length + Write-Status " Downloaded: $([math]::Round($size / 1MB, 1)) MB" + } catch { + if ($_.Exception.Response.StatusCode -eq 404) { + throw "Download failed: not found at $Url" + } + throw "Download failed for ${Url}: $($_.Exception.Message)" + } +} + +# -------------------------------------------------------------------------- +# Uninstall +# -------------------------------------------------------------------------- + +function Invoke-Uninstall { + Write-Step "Uninstalling Ollama" + + $regKey = Find-InnoSetupInstall + if (-not $regKey) { + Write-Host "Ollama is not installed." + return + } + + $uninstallString = (Get-ItemProperty -Path $regKey).UninstallString + if (-not $uninstallString) { + Write-Warning "No uninstall string found in registry" + return + } + + # Strip quotes if present + $uninstallExe = $uninstallString -replace '"', '' + Write-Status " Uninstaller: $uninstallExe" + + if (-not (Test-Path $uninstallExe)) { + Write-Warning "Uninstaller not found at: $uninstallExe" + return + } + + Write-Host "Launching uninstaller..." + # Run with GUI so user can choose whether to keep models + Start-Process -FilePath $uninstallExe -Wait + + # Verify removal + if (Find-InnoSetupInstall) { + Write-Warning "Uninstall may not have completed" + } else { + Write-Host "Ollama has been uninstalled." + } +} + +# -------------------------------------------------------------------------- +# Install +# -------------------------------------------------------------------------- + +function Invoke-Install { + # Determine installer URL + if ($Version) { + $installerUrl = "$DownloadBaseURL/OllamaSetup.exe?version=$Version" + } else { + $installerUrl = "$DownloadBaseURL/OllamaSetup.exe" + } + + # Download installer + Write-Step "Downloading Ollama" + if (-not $DebugInstall) { + Write-Host "Downloading Ollama..." + } + + $tempInstaller = Join-Path $env:TEMP "OllamaSetup.exe" + Invoke-Download -Url $installerUrl -OutFile $tempInstaller + + # Verify signature + Write-Step "Verifying signature" + if (-not (Test-Signature -FilePath $tempInstaller)) { + Remove-Item $tempInstaller -Force -ErrorAction SilentlyContinue + throw "Installer signature verification failed" + } + + # Build installer arguments + $installerArgs = "/VERYSILENT /NORESTART /SUPPRESSMSGBOXES" + if ($InstallDir) { + $installerArgs += " /DIR=`"$InstallDir`"" + } + Write-Status " Installer args: $installerArgs" + + # Run installer + Write-Step "Installing Ollama" + if (-not $DebugInstall) { + Write-Host "Installing..." + } + + # Create upgrade marker so the app starts hidden + # The app checks for this file on startup and removes it after + $markerDir = Join-Path $env:LOCALAPPDATA "Ollama" + $markerFile = Join-Path $markerDir "upgraded" + if (-not (Test-Path $markerDir)) { + New-Item -ItemType Directory -Path $markerDir -Force | Out-Null + } + New-Item -ItemType File -Path $markerFile -Force | Out-Null + Write-Status " Created upgrade marker: $markerFile" + + # Start installer and wait for just the installer process (not children) + # Using -Wait would wait for Ollama to exit too, which we don't want + $proc = Start-Process -FilePath $tempInstaller ` + -ArgumentList $installerArgs ` + -PassThru + $proc.WaitForExit() + + if ($proc.ExitCode -ne 0) { + Remove-Item $tempInstaller -Force -ErrorAction SilentlyContinue + throw "Installation failed with exit code $($proc.ExitCode)" + } + + # Cleanup + Remove-Item $tempInstaller -Force -ErrorAction SilentlyContinue + + # Update PATH in current session so 'ollama' works immediately + Write-Step "Updating session PATH" + Update-SessionPath + + Write-Host "Install complete. You can now run 'ollama'." +} + +# -------------------------------------------------------------------------- +# Main +# -------------------------------------------------------------------------- + +if ($Uninstall) { + Invoke-Uninstall +} else { + Invoke-Install +} diff --git a/scripts/install.sh b/scripts/install.sh index 804db04df..c34bdfc69 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -35,6 +35,8 @@ case "$ARCH" in *) error "Unsupported architecture: $ARCH" ;; esac +VER_PARAM="${OLLAMA_VERSION:+?version=$OLLAMA_VERSION}" + ########################################### # macOS ########################################### @@ -49,11 +51,7 @@ if [ "$OS" = "Darwin" ]; then exit 1 fi - if [ -n "${OLLAMA_VERSION:-}" ]; then - DOWNLOAD_URL="https://github.com/ollama/ollama/releases/download/${OLLAMA_VERSION}/Ollama-darwin.zip" - else - DOWNLOAD_URL="https://github.com/ollama/ollama/releases/latest/download/Ollama-darwin.zip" - fi + DOWNLOAD_URL="https://ollama.com/download/Ollama-darwin.zip${VER_PARAM}" if pgrep -x Ollama >/dev/null 2>&1; then status "Stopping running Ollama instance..." @@ -103,8 +101,6 @@ case "$KERN" in *) ;; esac -VER_PARAM="${OLLAMA_VERSION:+?version=$OLLAMA_VERSION}" - SUDO= if [ "$(id -u)" -ne 0 ]; then # Running as root, no need for sudo