Files

56 lines
3.7 KiB
PowerShell
Raw Permalink Normal View History

2026-09-06 17:04:11 +08:00
param([Parameter(Mandatory=$true)][string]$Archive)
$ErrorActionPreference = 'Stop'
$archivePath = (Resolve-Path -LiteralPath $Archive).Path
$archiveDirectory = Split-Path -Parent $archivePath
$sidecarPath = $archivePath + '.sha256'
if (-not (Test-Path -LiteralPath $sidecarPath -PathType Leaf)) { throw 'Archive checksum sidecar is missing' }
$sidecarHash = ((Get-Content -LiteralPath $sidecarPath -Raw).Trim() -split '\s+')[0]
$archiveHash = (Get-FileHash -LiteralPath $archivePath -Algorithm SHA256).Hash.ToLowerInvariant()
if ($sidecarHash -ne $archiveHash) { throw 'Archive checksum sidecar does not match' }
Add-Type -AssemblyName System.IO.Compression.FileSystem
$zip = [IO.Compression.ZipFile]::OpenRead($archivePath)
try {
$forbidden = @($zip.Entries | Where-Object {
$_.FullName -match '(^|/)(\.env|node_modules|runtime)(/|$)' -or
$_.FullName -match '\.(pem|key|p12|pfx)$'
})
if ($forbidden.Count -gt 0) { throw ('Bundle contains forbidden entries: ' + ($forbidden.FullName -join ', ')) }
$entryCount = $zip.Entries.Count
} finally {
$zip.Dispose()
}
$verificationRoot = Join-Path $archiveDirectory ('.verify-' + [Guid]::NewGuid().ToString('N'))
New-Item -ItemType Directory -Path $verificationRoot | Out-Null
try {
Expand-Archive -LiteralPath $archivePath -DestinationPath $verificationRoot
$payload = @(Get-ChildItem -LiteralPath $verificationRoot -Directory)
if ($payload.Count -ne 1) { throw 'Bundle must contain exactly one top-level directory' }
$manifestPath = Join-Path $payload[0].FullName 'deploy-manifest.json'
$checksumsPath = Join-Path $payload[0].FullName 'SHA256SUMS'
$manifest = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json
$expectedMigration = (Get-ChildItem -LiteralPath (Join-Path $payload[0].FullName 'migrations') -Filter '*.up.sql' | ForEach-Object { [int]($_.Name -split '_')[0] } | Measure-Object -Maximum).Maximum
if ($manifest.schema_version -ne 'server-deployment-bundle-v1' -or $manifest.schema_migration_target -ne $expectedMigration -or $expectedMigration -le 0 -or $manifest.hardware_runtime -ne 'volc_hardware_agent') {
2026-09-06 17:04:11 +08:00
throw 'Deployment manifest metadata is invalid'
}
$verifiedFiles = 0
foreach ($line in Get-Content -LiteralPath $checksumsPath) {
if ($line -notmatch '^([0-9a-f]{64}) (.+)$') { throw "Invalid checksum line: $line" }
$target = [IO.Path]::GetFullPath((Join-Path $payload[0].FullName $Matches[2]))
if (-not $target.StartsWith($payload[0].FullName + [IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase)) { throw 'Checksum target escapes bundle root' }
if (-not (Test-Path -LiteralPath $target -PathType Leaf)) { throw "Checksum target is missing: $($Matches[2])" }
if ((Get-FileHash -LiteralPath $target -Algorithm SHA256).Hash.ToLowerInvariant() -ne $Matches[1]) { throw "Checksum mismatch: $($Matches[2])" }
$verifiedFiles++
}
if ($verifiedFiles -ne ($manifest.files.Count + 1)) { throw 'Checksum coverage does not match manifest plus manifest file' }
[ordered]@{ archive = $archivePath; sha256 = $archiveHash; zip_entries = $entryCount; verified_files = $verifiedFiles; candidate_version = $manifest.candidate_version; schema_migration_target = $manifest.schema_migration_target } | ConvertTo-Json -Compress
} finally {
$resolvedVerificationRoot = [IO.Path]::GetFullPath($verificationRoot)
$resolvedArchiveDirectory = [IO.Path]::GetFullPath($archiveDirectory)
if ($resolvedVerificationRoot.StartsWith($resolvedArchiveDirectory + [IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase) -and (Test-Path -LiteralPath $resolvedVerificationRoot)) {
Remove-Item -LiteralPath $resolvedVerificationRoot -Recurse -Force
}
}