feat(lab): seal reusable ffmpeg runtime asset
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$TargetRoot = (
|
||||
"D:\NDC_MISSIONCORE\runtime\assets\observatory-portable\" +
|
||||
"eomt-ffmpeg-runtime-frigate-8a364092b03561b9-v1"
|
||||
)
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ProgressPreference = "SilentlyContinue"
|
||||
|
||||
$SourceImageSha256 = "8a364092b03561b9c08ac00730206e363a53d07ea0304f7d543b403b65432b5e"
|
||||
$SourceImage = "sha256:$SourceImageSha256"
|
||||
$SourcePath = "/usr/lib/ffmpeg/7.0"
|
||||
$AssetId = "eomt-ffmpeg-runtime"
|
||||
$IdentityAlgorithm = "relative-path-tab-size-tab-file-sha256-lf/v1"
|
||||
|
||||
function Assert-LastExitCode {
|
||||
param([string]$Operation)
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "$Operation failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
}
|
||||
|
||||
function Get-Sha256Hex {
|
||||
param([byte[]]$Payload)
|
||||
|
||||
$algorithm = [Security.Cryptography.SHA256]::Create()
|
||||
try {
|
||||
return -join @($algorithm.ComputeHash($Payload) | ForEach-Object {
|
||||
$_.ToString("x2", [Globalization.CultureInfo]::InvariantCulture)
|
||||
})
|
||||
}
|
||||
finally {
|
||||
$algorithm.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
$target = [IO.Path]::GetFullPath($TargetRoot)
|
||||
$allowedParent = [IO.Path]::GetFullPath(
|
||||
"D:\NDC_MISSIONCORE\runtime\assets\observatory-portable"
|
||||
).TrimEnd("\")
|
||||
if (
|
||||
[IO.Path]::GetPathRoot($target).TrimEnd("\") -ine "D:" -or
|
||||
(Split-Path -Parent $target) -ine $allowedParent -or
|
||||
(Split-Path -Leaf $target) -ne (
|
||||
"eomt-ffmpeg-runtime-frigate-8a364092b03561b9-v1"
|
||||
)
|
||||
) {
|
||||
throw "LAB V1 FFmpeg runtime target is outside its fixed local asset root"
|
||||
}
|
||||
if (Test-Path -LiteralPath $target) {
|
||||
throw "LAB V1 FFmpeg runtime target already exists"
|
||||
}
|
||||
|
||||
$parent = Split-Path -Parent $target
|
||||
$null = New-Item -ItemType Directory -Path $parent -Force
|
||||
$staging = "$target.staging-$([Guid]::NewGuid().ToString('N'))"
|
||||
$null = New-Item -ItemType Directory -Path $staging
|
||||
$containerId = $null
|
||||
$published = $false
|
||||
|
||||
try {
|
||||
$inspectedImage = (& docker image inspect --format "{{.Id}}" $SourceImage).Trim()
|
||||
Assert-LastExitCode "Exact FFmpeg source image inspection"
|
||||
if ($inspectedImage -ne $SourceImage) {
|
||||
throw "Installed FFmpeg source image identity changed"
|
||||
}
|
||||
|
||||
$containerId = (& docker create `
|
||||
--network none `
|
||||
--read-only `
|
||||
--cap-drop ALL `
|
||||
--security-opt no-new-privileges `
|
||||
--entrypoint /bin/true `
|
||||
$SourceImage).Trim()
|
||||
Assert-LastExitCode "FFmpeg source container creation"
|
||||
if ($containerId -notmatch "^[a-f0-9]{64}$") {
|
||||
throw "FFmpeg source container id is invalid"
|
||||
}
|
||||
|
||||
& docker cp "${containerId}:${SourcePath}/." $staging
|
||||
Assert-LastExitCode "FFmpeg runtime extraction"
|
||||
|
||||
$prefix = $staging.TrimEnd("\") + "\"
|
||||
$records = @()
|
||||
[int64]$totalBytes = 0
|
||||
foreach ($file in @(Get-ChildItem -LiteralPath $staging -Recurse -File -Force)) {
|
||||
if ($file.Attributes -band [IO.FileAttributes]::ReparsePoint) {
|
||||
throw "FFmpeg runtime contains a reparse point"
|
||||
}
|
||||
if (-not $file.FullName.StartsWith($prefix, [StringComparison]::OrdinalIgnoreCase)) {
|
||||
throw "FFmpeg runtime member escaped staging"
|
||||
}
|
||||
$relative = $file.FullName.Substring($prefix.Length).Replace("\", "/")
|
||||
if (
|
||||
-not $relative -or
|
||||
$relative.StartsWith("/") -or
|
||||
$relative.Split("/") -contains ".."
|
||||
) {
|
||||
throw "FFmpeg runtime member path is invalid"
|
||||
}
|
||||
$digest = (Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
$records += [pscustomobject]@{
|
||||
relative_path = $relative
|
||||
byte_length = [int64]$file.Length
|
||||
sha256 = $digest
|
||||
}
|
||||
$totalBytes += [int64]$file.Length
|
||||
}
|
||||
$records = @($records | Sort-Object -CaseSensitive -Property relative_path)
|
||||
if ($records.Count -lt 2) {
|
||||
throw "FFmpeg runtime inventory is incomplete"
|
||||
}
|
||||
|
||||
$builder = New-Object Text.StringBuilder
|
||||
foreach ($record in $records) {
|
||||
$null = $builder.Append($record.relative_path)
|
||||
$null = $builder.Append([char]9)
|
||||
$null = $builder.Append($record.byte_length)
|
||||
$null = $builder.Append([char]9)
|
||||
$null = $builder.Append($record.sha256)
|
||||
$null = $builder.Append([char]10)
|
||||
}
|
||||
$utf8 = New-Object Text.UTF8Encoding($false)
|
||||
$manifestBytes = $utf8.GetBytes($builder.ToString())
|
||||
$identitySha256 = Get-Sha256Hex $manifestBytes
|
||||
[IO.File]::WriteAllBytes(
|
||||
(Join-Path $staging "tree-manifest.tsv"),
|
||||
$manifestBytes
|
||||
)
|
||||
|
||||
$ffmpeg = @($records | Where-Object relative_path -eq "bin/ffmpeg")
|
||||
$ffprobe = @($records | Where-Object relative_path -eq "bin/ffprobe")
|
||||
if ($ffmpeg.Count -ne 1 -or $ffprobe.Count -ne 1) {
|
||||
throw "FFmpeg runtime binaries are absent or ambiguous"
|
||||
}
|
||||
$receipt = [ordered]@{
|
||||
schema_version = "missioncore.sealed-tree-runtime/v1"
|
||||
asset_id = $AssetId
|
||||
identity_algorithm = $IdentityAlgorithm
|
||||
identity_sha256 = $identitySha256
|
||||
source_image_sha256 = $SourceImageSha256
|
||||
source_path = $SourcePath
|
||||
file_count = $records.Count
|
||||
byte_length = $totalBytes
|
||||
manifest_relative_path = "tree-manifest.tsv"
|
||||
binaries = [ordered]@{
|
||||
ffmpeg = [ordered]@{
|
||||
relative_path = "bin/ffmpeg"
|
||||
byte_length = $ffmpeg[0].byte_length
|
||||
sha256 = $ffmpeg[0].sha256
|
||||
}
|
||||
ffprobe = [ordered]@{
|
||||
relative_path = "bin/ffprobe"
|
||||
byte_length = $ffprobe[0].byte_length
|
||||
sha256 = $ffprobe[0].sha256
|
||||
}
|
||||
}
|
||||
}
|
||||
[IO.File]::WriteAllText(
|
||||
(Join-Path $staging "tree-receipt.json"),
|
||||
(($receipt | ConvertTo-Json -Depth 8) + [char]10),
|
||||
$utf8
|
||||
)
|
||||
|
||||
Move-Item -LiteralPath $staging -Destination $target
|
||||
$published = $true
|
||||
[pscustomobject]@{
|
||||
path = $target
|
||||
identity_sha256 = $identitySha256
|
||||
file_count = $records.Count
|
||||
byte_length = $totalBytes
|
||||
ffmpeg_sha256 = $ffmpeg[0].sha256
|
||||
ffprobe_sha256 = $ffprobe[0].sha256
|
||||
} | ConvertTo-Json -Compress
|
||||
}
|
||||
finally {
|
||||
if ($containerId -match "^[a-f0-9]{64}$") {
|
||||
& docker rm -f $containerId *> $null
|
||||
}
|
||||
if (-not $published -and (Test-Path -LiteralPath $staging)) {
|
||||
Remove-Item -LiteralPath $staging -Recurse -Force
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user