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