Files
story/scripts/build-server-deployment-bundle.ps1
T

88 lines
4.9 KiB
PowerShell
Raw Normal View History

2026-09-06 17:04:11 +08:00
param(
[string]$Version = '0.76.0-rc.hardware1',
[string]$OutputDirectory = '.deploy-artifacts'
)
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true
$projectRoot = (Resolve-Path -LiteralPath (Split-Path -Parent $PSScriptRoot)).Path
$outputRoot = [IO.Path]::GetFullPath((Join-Path $projectRoot $OutputDirectory))
if (-not $outputRoot.StartsWith($projectRoot + [IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase)) {
throw 'OutputDirectory must resolve inside the project workspace'
}
if ($Version -notmatch '^[0-9A-Za-z][0-9A-Za-z._-]{0,79}$') {
throw 'Version contains unsupported characters'
}
New-Item -ItemType Directory -Force -Path $outputRoot | Out-Null
$bundleName = "data-terminal-cloud-$Version"
$stagingRoot = Join-Path $outputRoot ('.staging-' + [Guid]::NewGuid().ToString('N'))
$payloadRoot = Join-Path $stagingRoot $bundleName
New-Item -ItemType Directory -Force -Path $payloadRoot | Out-Null
try {
$candidatePaths = @(& git ls-files --cached --others --exclude-standard -- `
cmd internal migrations openapi web deploy compose.yaml go.mod go.sum README.md CHANGELOG.md .env.example)
if ($LASTEXITCODE -ne 0) { throw 'git file enumeration failed' }
$candidatePaths = $candidatePaths | Where-Object {
$_ -and
$_ -notmatch '(^|/)(node_modules|dist|coverage|runtime|release)(/|$)' -and
$_ -notmatch '(^|/)\.env$' -and
$_ -notmatch '\.(pem|key|p12|pfx)$'
} | Sort-Object -Unique
if ($candidatePaths.Count -lt 20) { throw 'deployment file set is unexpectedly small' }
foreach ($relativePath in $candidatePaths) {
$sourcePath = [IO.Path]::GetFullPath((Join-Path $projectRoot $relativePath))
if (-not $sourcePath.StartsWith($projectRoot + [IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase) -or -not (Test-Path -LiteralPath $sourcePath -PathType Leaf)) {
throw "Unsafe or missing deployment source path: $relativePath"
}
$targetPath = Join-Path $payloadRoot $relativePath
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $targetPath) | Out-Null
Copy-Item -LiteralPath $sourcePath -Destination $targetPath
}
$sourceCommit = (& git rev-parse HEAD).Trim()
if ($LASTEXITCODE -ne 0 -or $sourceCommit -notmatch '^[0-9a-f]{40}$') { throw 'source commit could not be resolved' }
$statusLines = @(& git status --porcelain=v1)
if ($LASTEXITCODE -ne 0) { throw 'working tree status could not be resolved' }
$files = Get-ChildItem -LiteralPath $payloadRoot -File -Recurse | Sort-Object FullName | ForEach-Object {
[ordered]@{
path = $_.FullName.Substring($payloadRoot.Length + 1).Replace('\','/')
size_bytes = $_.Length
sha256 = (Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash.ToLowerInvariant()
}
}
$manifest = [ordered]@{
schema_version = 'server-deployment-bundle-v1'
candidate_version = $Version
generated_at = [DateTime]::UtcNow.ToString('o')
source_commit = $sourceCommit
working_tree_dirty = $statusLines.Count -gt 0
schema_migration_target = 52
hardware_runtime = 'volc_hardware_agent'
files = @($files)
}
$manifestPath = Join-Path $payloadRoot 'deploy-manifest.json'
[IO.File]::WriteAllText($manifestPath, (($manifest | ConvertTo-Json -Depth 8) + [Environment]::NewLine), [Text.UTF8Encoding]::new($false))
$checksumLines = Get-ChildItem -LiteralPath $payloadRoot -File -Recurse | Where-Object Name -ne 'SHA256SUMS' | Sort-Object FullName | ForEach-Object {
$relative = $_.FullName.Substring($payloadRoot.Length + 1).Replace('\','/')
"{0} {1}" -f (Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash.ToLowerInvariant(), $relative
}
[IO.File]::WriteAllLines((Join-Path $payloadRoot 'SHA256SUMS'), [string[]]$checksumLines, [Text.UTF8Encoding]::new($false))
$archivePath = Join-Path $outputRoot ($bundleName + '.zip')
if (Test-Path -LiteralPath $archivePath) { Remove-Item -LiteralPath $archivePath -Force }
Compress-Archive -LiteralPath $payloadRoot -DestinationPath $archivePath -CompressionLevel Optimal
$archiveHash = (Get-FileHash -LiteralPath $archivePath -Algorithm SHA256).Hash.ToLowerInvariant()
"$archiveHash $([IO.Path]::GetFileName($archivePath))" | Set-Content -LiteralPath ($archivePath + '.sha256') -Encoding ascii
[ordered]@{ archive = $archivePath; sha256 = $archiveHash; files = $files.Count; source_commit = $sourceCommit; working_tree_dirty = $statusLines.Count -gt 0 } | ConvertTo-Json -Compress
} finally {
$resolvedStaging = [IO.Path]::GetFullPath($stagingRoot)
if ($resolvedStaging.StartsWith($outputRoot + [IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase) -and (Test-Path -LiteralPath $resolvedStaging)) {
Remove-Item -LiteralPath $resolvedStaging -Recurse -Force
}
}