Files
story/scripts/verify-server-deployment-bundle.ps1
T
Codex Local bf1233d5df
ci / verify (push) Canceled after 0s
feat: expand companion platform, device controls, and memoir workflows
Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
2026-10-08 10:06:44 +08:00

56 lines
3.7 KiB
PowerShell

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') {
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
}
}