| [CmdletBinding()] |
| param( |
| [Parameter(Mandatory=$true)][string]$ManifestUrl, |
| [switch]$Apply |
| ) |
| $ErrorActionPreference = "Stop" |
| $Root = Resolve-Path (Join-Path $PSScriptRoot "..") |
| $tmp = Join-Path $Root ".jackailocal\updates" |
| New-Item -ItemType Directory -Force -Path $tmp | Out-Null |
| $manifestPath = Join-Path $tmp "update-manifest.json" |
| $sigPath = Join-Path $tmp "update-manifest.json.sig" |
| $pubKeyPath = Join-Path $Root "config\update-public-key.xml" |
|
|
| if (!(Test-Path $pubKeyPath)) { |
| throw "Public key for update verification not found at: $pubKeyPath" |
| } |
|
|
| |
| Invoke-WebRequest -Uri $ManifestUrl -OutFile $manifestPath |
| Invoke-WebRequest -Uri "$ManifestUrl.sig" -OutFile $sigPath |
|
|
| |
| & (Join-Path $PSScriptRoot "Verify-SignedManifest.ps1") -ManifestPath $manifestPath -SignaturePath $sigPath -PublicKeyXmlPath $pubKeyPath |
|
|
| $manifest = Get-Content $manifestPath -Raw | ConvertFrom-Json |
| if ($manifest.product -ne "JackAILocal") { throw "Wrong product." } |
|
|
| $rootPath = [System.IO.Path]::GetFullPath($Root) |
|
|
| foreach ($file in $manifest.files) { |
| |
| $dest = [System.IO.Path]::GetFullPath((Join-Path $rootPath $file.path)) |
| if (-not $dest.StartsWith($rootPath, [System.StringComparison]::OrdinalIgnoreCase)) { |
| throw "Security error: Path traversal detected in file path: $($file.path)" |
| } |
|
|
| $download = Join-Path $tmp ([IO.Path]::GetFileName($file.path)) |
| Invoke-WebRequest -Uri $file.url -OutFile $download |
| $h = (Get-FileHash -Algorithm SHA256 $download).Hash.ToLowerInvariant() |
| if ($h -ne $file.sha256.ToLowerInvariant()) { throw "Checksum failed: $($file.path)" } |
| if ($Apply) { |
| New-Item -ItemType Directory -Force -Path (Split-Path $dest) | Out-Null |
| Copy-Item $download $dest -Force |
| } |
| } |
| if (-not $Apply) { Write-Host "Dry-run update verified. Use -Apply to install." } |
|
|