You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

700 lines
51 KiB

param(
[string]$StagingRoot = "",
[ValidateSet("systemd", "buildroot")]
[string]$InitSystem = "systemd",
[switch]$KeepStaging
)
$ErrorActionPreference = "Stop"
function Assert-FileNonEmpty {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path)) {
throw "Expected staged file is missing: $Path"
}
$item = Get-Item -LiteralPath $Path
if ($item.Length -le 0) {
throw "Expected staged file is empty: $Path"
}
}
function Assert-Contains {
param(
[string]$Text,
[string]$Expected,
[string]$Message
)
if (-not $Text.Contains($Expected)) {
throw $Message
}
}
function Assert-NoSecretEnvironmentValues {
param([string]$Text)
foreach ($line in ($Text -split "`r?`n")) {
if ($line -notmatch "^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$") {
continue
}
$key = $Matches[1]
$value = $Matches[2].Trim()
if ($value -eq "") {
continue
}
if ($key -eq "TERA_API_PASSWORD_KDF" -or $key -eq "TERA_API_MIN_PASSWORD_LENGTH" -or $key -eq "TERA_API_SECRET_STORE") {
continue
}
if ($key -match "(?i)(PASSWORD|TOKEN|SECRET|PRIVATE_KEY)") {
throw "target env must not contain secret material in $key"
}
}
}
$scriptApiRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
$workingApiRoot = Resolve-Path (Get-Location)
if (Test-Path -LiteralPath (Join-Path $workingApiRoot "go.mod")) {
$apiRoot = $workingApiRoot
} elseif (Test-Path -LiteralPath (Join-Path $scriptApiRoot "go.mod")) {
$apiRoot = $scriptApiRoot
} else {
throw "Run this script from services\tera-api, or place it under services\tera-api\scripts."
}
$repoRoot = Resolve-Path (Join-Path $apiRoot "..\..")
$projectRoot = Resolve-Path (Join-Path $apiRoot "..")
$buildScript = Join-Path $apiRoot "scripts\check-linux-arm64-build.ps1"
$unitSource = Join-Path $apiRoot "deploy\systemd\tera-api.service"
$envSource = Join-Path $apiRoot "deploy\etc\tera\tera-api.env"
$accessdUnitSource = Join-Path $apiRoot "deploy\systemd\tera-accessd.service"
$accessdEnvSource = Join-Path $apiRoot "deploy\etc\tera\tera-accessd.env"
$hwdUnitSource = Join-Path $apiRoot "deploy\systemd\tera-hwd.service"
$hwdEnvSource = Join-Path $apiRoot "deploy\etc\tera\tera-hwd.env"
$netdUnitSource = Join-Path $apiRoot "deploy\systemd\tera-netd.service"
$netdEnvSource = Join-Path $apiRoot "deploy\etc\tera\tera-netd.env"
$timedUnitSource = Join-Path $apiRoot "deploy\systemd\tera-timed.service"
$timedEnvSource = Join-Path $apiRoot "deploy\etc\tera\tera-timed.env"
$alarmdUnitSource = Join-Path $apiRoot "deploy\systemd\tera-alarmd.service"
$alarmdEnvSource = Join-Path $apiRoot "deploy\etc\tera\tera-alarmd.env"
$snmpdUnitSource = Join-Path $apiRoot "deploy\systemd\tera-snmpd.service"
$snmpdBuildrootEnvSource = Join-Path $apiRoot "deploy\etc\tera\tera-snmpd.env"
$snmpdSystemdEnvSource = Join-Path $apiRoot "deploy\etc\tera\tera-snmpd-systemd.env"
$logdUnitSource = Join-Path $apiRoot "deploy\systemd\tera-logd.service"
$logdEnvSource = Join-Path $apiRoot "deploy\etc\tera\tera-logd.env"
$updatedUnitSource = Join-Path $apiRoot "deploy\systemd\tera-updated.service"
$updatedEnvSource = Join-Path $apiRoot "deploy\etc\tera\tera-updated.env"
$serialdUnitSource = Join-Path $apiRoot "deploy\systemd\tera-seriald.service"
$serialdEnvSource = Join-Path $apiRoot "deploy\etc\tera\tera-seriald.env"
$sessiondUnitSource = Join-Path $apiRoot "deploy\systemd\tera-sessiond.service"
$sessiondEnvSource = Join-Path $apiRoot "deploy\etc\tera\tera-sessiond.env"
$webUnitSource = Join-Path $apiRoot "deploy\systemd\tera-web.service"
$webEnvSource = Join-Path $apiRoot "deploy\etc\tera\tera-web.env"
$cloneDemoWebBuildScript = Join-Path $projectRoot "frontend\scripts\check-linux-arm64-build.ps1"
$cloneDemoWebUnitSource = Join-Path $apiRoot "deploy\systemd\tera-clone-demo-web.service"
$cloneDemoWebEnvSource = Join-Path $apiRoot "deploy\etc\tera\tera-clone-demo-web.env"
$webSource = Join-Path $repoRoot "webapp"
$serialMapSource = Join-Path $apiRoot "deploy\etc\tera\serial-map.json"
$updateTrustSource = Join-Path $apiRoot "deploy\etc\tera\update-trust"
$updateTrustReadmeSource = Join-Path $updateTrustSource "README"
$updateTrustAnchorSources = @(Get-ChildItem -LiteralPath $updateTrustSource -Force -File -Filter "*.json")
if ($updateTrustAnchorSources.Count -eq 0) {
throw "At least one reviewed public update trust anchor is required for target staging."
}
$buildrootSerialdSource = Join-Path $apiRoot "deploy\buildroot\etc\init.d\S40tera-seriald"
$buildrootHwdSource = Join-Path $apiRoot "deploy\buildroot\etc\init.d\S42tera-hwd"
$buildrootNetdSource = Join-Path $apiRoot "deploy\buildroot\etc\init.d\S43tera-netd"
$buildrootTimedSource = Join-Path $apiRoot "deploy\buildroot\etc\init.d\S45tera-timed"
$buildrootAlarmdSource = Join-Path $apiRoot "deploy\buildroot\etc\init.d\S47tera-alarmd"
$buildrootSNMPDSource = Join-Path $apiRoot "deploy\buildroot\etc\init.d\S46tera-snmpd"
$buildrootLogdSource = Join-Path $apiRoot "deploy\buildroot\etc\init.d\S44tera-logd"
$buildrootUpdatedSource = Join-Path $apiRoot "deploy\buildroot\etc\init.d\S48tera-updated"
$buildrootAPISource = Join-Path $apiRoot "deploy\buildroot\etc\init.d\S50tera-api"
$buildrootAccessdSource = Join-Path $apiRoot "deploy\buildroot\etc\init.d\S49tera-accessd"
$buildrootSessiondSource = Join-Path $apiRoot "deploy\buildroot\etc\init.d\S55tera-sessiond"
$buildrootWebSource = Join-Path $apiRoot "deploy\buildroot\etc\init.d\S60tera-web"
$buildrootCloneDemoWebSource = Join-Path $apiRoot "deploy\buildroot\etc\init.d\S61tera-clone-demo-web"
$buildrootUdevSource = Join-Path $apiRoot "deploy\buildroot\etc\udev\rules.d\99-tera-serial.rules"
$requiredPaths = @($buildScript, $cloneDemoWebBuildScript, $envSource, $accessdEnvSource, $hwdEnvSource, $netdEnvSource, $timedEnvSource, $alarmdEnvSource, $snmpdBuildrootEnvSource, $snmpdSystemdEnvSource, $logdEnvSource, $updatedEnvSource, $serialdEnvSource, $sessiondEnvSource, $webEnvSource, $cloneDemoWebEnvSource, $webSource, $serialMapSource, $updateTrustReadmeSource) + $updateTrustAnchorSources.FullName
if ($InitSystem -eq "systemd") {
$requiredPaths += @($unitSource, $accessdUnitSource, $hwdUnitSource, $netdUnitSource, $timedUnitSource, $alarmdUnitSource, $snmpdUnitSource, $logdUnitSource, $updatedUnitSource, $serialdUnitSource, $sessiondUnitSource, $webUnitSource, $cloneDemoWebUnitSource)
} else {
$requiredPaths += @($buildrootSerialdSource, $buildrootHwdSource, $buildrootNetdSource, $buildrootLogdSource, $buildrootTimedSource, $buildrootAlarmdSource, $buildrootSNMPDSource, $buildrootUpdatedSource, $buildrootAccessdSource, $buildrootAPISource, $buildrootSessiondSource, $buildrootWebSource, $buildrootCloneDemoWebSource, $buildrootUdevSource)
}
foreach ($requiredPath in $requiredPaths) {
if (-not (Test-Path -LiteralPath $requiredPath)) {
throw "Required packaging input is missing: $requiredPath"
}
}
$createdTempRoot = $false
if ($StagingRoot -eq "") {
$StagingRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("tera-api-rootfs-" + [System.Guid]::NewGuid().ToString("N"))
$createdTempRoot = $true
}
if (Test-Path -LiteralPath $StagingRoot) {
throw "Staging root already exists; choose an empty path: $StagingRoot"
}
$binTarget = Join-Path $StagingRoot "usr\bin\tera-api"
$accessdBinTarget = Join-Path $StagingRoot "usr\bin\tera-accessd"
$hwdBinTarget = Join-Path $StagingRoot "usr\bin\tera-hwd"
$netdBinTarget = Join-Path $StagingRoot "usr\bin\tera-netd"
$timedBinTarget = Join-Path $StagingRoot "usr\bin\tera-timed"
$alarmdBinTarget = Join-Path $StagingRoot "usr\bin\tera-alarmd"
$snmpdBinTarget = Join-Path $StagingRoot "usr\bin\tera-snmpd"
$logdBinTarget = Join-Path $StagingRoot "usr\bin\tera-logd"
$updatedBinTarget = Join-Path $StagingRoot "usr\bin\tera-updated"
$serialdBinTarget = Join-Path $StagingRoot "usr\bin\tera-seriald"
$sessiondBinTarget = Join-Path $StagingRoot "usr\bin\tera-sessiond"
$webBinTarget = Join-Path $StagingRoot "usr\bin\tera-web"
$cloneDemoWebBinTarget = Join-Path $StagingRoot "usr\bin\tera-clone-demo-web"
$serialCheckBinTarget = Join-Path $StagingRoot "usr\bin\tera-serial-check"
$serialLineCheckBinTarget = Join-Path $StagingRoot "usr\bin\tera-serial-line-check"
$unitTarget = Join-Path $StagingRoot "etc\systemd\system\tera-api.service"
$accessdUnitTarget = Join-Path $StagingRoot "etc\systemd\system\tera-accessd.service"
$hwdUnitTarget = Join-Path $StagingRoot "etc\systemd\system\tera-hwd.service"
$netdUnitTarget = Join-Path $StagingRoot "etc\systemd\system\tera-netd.service"
$timedUnitTarget = Join-Path $StagingRoot "etc\systemd\system\tera-timed.service"
$alarmdUnitTarget = Join-Path $StagingRoot "etc\systemd\system\tera-alarmd.service"
$snmpdUnitTarget = Join-Path $StagingRoot "etc\systemd\system\tera-snmpd.service"
$logdUnitTarget = Join-Path $StagingRoot "etc\systemd\system\tera-logd.service"
$updatedUnitTarget = Join-Path $StagingRoot "etc\systemd\system\tera-updated.service"
$serialdUnitTarget = Join-Path $StagingRoot "etc\systemd\system\tera-seriald.service"
$sessiondUnitTarget = Join-Path $StagingRoot "etc\systemd\system\tera-sessiond.service"
$webUnitTarget = Join-Path $StagingRoot "etc\systemd\system\tera-web.service"
$cloneDemoWebUnitTarget = Join-Path $StagingRoot "etc\systemd\system\tera-clone-demo-web.service"
$envTarget = Join-Path $StagingRoot "etc\tera\tera-api.env"
$accessdEnvTarget = Join-Path $StagingRoot "etc\tera\tera-accessd.env"
$hwdEnvTarget = Join-Path $StagingRoot "etc\tera\tera-hwd.env"
$netdEnvTarget = Join-Path $StagingRoot "etc\tera\tera-netd.env"
$timedEnvTarget = Join-Path $StagingRoot "etc\tera\tera-timed.env"
$alarmdEnvTarget = Join-Path $StagingRoot "etc\tera\tera-alarmd.env"
$snmpdEnvTarget = Join-Path $StagingRoot "etc\tera\tera-snmpd.env"
$logdEnvTarget = Join-Path $StagingRoot "etc\tera\tera-logd.env"
$updatedEnvTarget = Join-Path $StagingRoot "etc\tera\tera-updated.env"
$serialdEnvTarget = Join-Path $StagingRoot "etc\tera\tera-seriald.env"
$sessiondEnvTarget = Join-Path $StagingRoot "etc\tera\tera-sessiond.env"
$webEnvTarget = Join-Path $StagingRoot "etc\tera\tera-web.env"
$cloneDemoWebEnvTarget = Join-Path $StagingRoot "etc\tera\tera-clone-demo-web.env"
$webRootTarget = Join-Path $StagingRoot "usr\share\tera-web"
$serialMapTarget = Join-Path $StagingRoot "etc\tera\serial-map.json"
$updateTrustReadmeTarget = Join-Path $StagingRoot "etc\tera\update-trust\README"
$updateTrustAnchorTargets = @($updateTrustAnchorSources | ForEach-Object { Join-Path (Split-Path -Parent $updateTrustReadmeTarget) $_.Name })
$buildrootSerialdTarget = Join-Path $StagingRoot "etc\init.d\S40tera-seriald"
$buildrootHwdTarget = Join-Path $StagingRoot "etc\init.d\S42tera-hwd"
$buildrootNetdTarget = Join-Path $StagingRoot "etc\init.d\S43tera-netd"
$buildrootTimedTarget = Join-Path $StagingRoot "etc\init.d\S45tera-timed"
$buildrootAlarmdTarget = Join-Path $StagingRoot "etc\init.d\S47tera-alarmd"
$buildrootSNMPDTarget = Join-Path $StagingRoot "etc\init.d\S46tera-snmpd"
$buildrootLogdTarget = Join-Path $StagingRoot "etc\init.d\S44tera-logd"
$buildrootUpdatedTarget = Join-Path $StagingRoot "etc\init.d\S48tera-updated"
$buildrootAPITarget = Join-Path $StagingRoot "etc\init.d\S50tera-api"
$buildrootAccessdTarget = Join-Path $StagingRoot "etc\init.d\S49tera-accessd"
$buildrootSessiondTarget = Join-Path $StagingRoot "etc\init.d\S55tera-sessiond"
$buildrootWebTarget = Join-Path $StagingRoot "etc\init.d\S60tera-web"
$buildrootCloneDemoWebTarget = Join-Path $StagingRoot "etc\init.d\S61tera-clone-demo-web"
$buildrootUdevTarget = Join-Path $StagingRoot "etc\udev\rules.d\99-tera-serial.rules"
$stateDir = Join-Path $StagingRoot "var\lib\tera-api"
$logDir = Join-Path $StagingRoot "var\log\tera"
try {
foreach ($directory in @(
(Split-Path -Parent $binTarget),
(Split-Path -Parent $unitTarget),
(Split-Path -Parent $envTarget),
$webRootTarget,
(Split-Path -Parent $buildrootSerialdTarget),
(Split-Path -Parent $buildrootUdevTarget),
$stateDir,
$logDir
)) {
New-Item -ItemType Directory -Force $directory | Out-Null
}
& $buildScript -OutputPath $binTarget -Command tera-api
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
& $buildScript -OutputPath $accessdBinTarget -Command tera-accessd
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
& $buildScript -OutputPath $hwdBinTarget -Command tera-hwd
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
& $buildScript -OutputPath $netdBinTarget -Command tera-netd
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
& $buildScript -OutputPath $timedBinTarget -Command tera-timed
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
& $buildScript -OutputPath $alarmdBinTarget -Command tera-alarmd
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
& $buildScript -OutputPath $snmpdBinTarget -Command tera-snmpd
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
& $buildScript -OutputPath $logdBinTarget -Command tera-logd
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
& $buildScript -OutputPath $updatedBinTarget -Command tera-updated
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
& $buildScript -OutputPath $serialdBinTarget -Command tera-seriald
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
& $buildScript -OutputPath $sessiondBinTarget -Command tera-sessiond
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
& $buildScript -OutputPath $webBinTarget -Command tera-web
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
& $cloneDemoWebBuildScript -OutputPath $cloneDemoWebBinTarget
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
& $buildScript -OutputPath $serialCheckBinTarget -Command tera-serial-check
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
& $buildScript -OutputPath $serialLineCheckBinTarget -Command tera-serial-line-check
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
Copy-Item -LiteralPath $envSource -Destination $envTarget -Force
Copy-Item -LiteralPath $accessdEnvSource -Destination $accessdEnvTarget -Force
Copy-Item -LiteralPath $hwdEnvSource -Destination $hwdEnvTarget -Force
Copy-Item -LiteralPath $netdEnvSource -Destination $netdEnvTarget -Force
Copy-Item -LiteralPath $timedEnvSource -Destination $timedEnvTarget -Force
Copy-Item -LiteralPath $alarmdEnvSource -Destination $alarmdEnvTarget -Force
Copy-Item -LiteralPath $logdEnvSource -Destination $logdEnvTarget -Force
Copy-Item -LiteralPath $updatedEnvSource -Destination $updatedEnvTarget -Force
Copy-Item -LiteralPath $serialdEnvSource -Destination $serialdEnvTarget -Force
Copy-Item -LiteralPath $sessiondEnvSource -Destination $sessiondEnvTarget -Force
Copy-Item -LiteralPath $webEnvSource -Destination $webEnvTarget -Force
Copy-Item -LiteralPath $cloneDemoWebEnvSource -Destination $cloneDemoWebEnvTarget -Force
Copy-Item -LiteralPath (Join-Path $webSource "index.html") -Destination (Join-Path $webRootTarget "index.html") -Force
Copy-Item -LiteralPath (Join-Path $webSource "src") -Destination (Join-Path $webRootTarget "src") -Recurse -Force
Copy-Item -LiteralPath $serialMapSource -Destination $serialMapTarget -Force
New-Item -ItemType Directory -Force (Split-Path -Parent $updateTrustReadmeTarget) | Out-Null
Copy-Item -LiteralPath $updateTrustReadmeSource -Destination $updateTrustReadmeTarget -Force
foreach ($anchorSource in $updateTrustAnchorSources) {
if (($anchorSource.Attributes -band [IO.FileAttributes]::ReparsePoint) -or
$anchorSource.Name.EndsWith(".private.json", [StringComparison]::OrdinalIgnoreCase)) {
throw "Update trust anchor must be a public regular file: $($anchorSource.FullName)"
}
$anchor = Get-Content -LiteralPath $anchorSource.FullName -Raw -Encoding UTF8 | ConvertFrom-Json
if ($anchor.format_version -ne 1 -or $anchor.algorithm -ne "ed25519" -or
$anchorSource.Name -cne "$($anchor.key_id).json" -or
$anchor.allowed_products -notcontains "tera-core-x48-pro" -or
$anchor.allowed_profiles -notcontains "fet3568j-c") {
throw "Update trust anchor policy is invalid: $($anchorSource.FullName)"
}
Copy-Item -LiteralPath $anchorSource.FullName -Destination (Join-Path (Split-Path -Parent $updateTrustReadmeTarget) $anchorSource.Name) -Force
}
if ($InitSystem -eq "systemd") {
Copy-Item -LiteralPath $snmpdSystemdEnvSource -Destination $snmpdEnvTarget -Force
Copy-Item -LiteralPath $unitSource -Destination $unitTarget -Force
Copy-Item -LiteralPath $accessdUnitSource -Destination $accessdUnitTarget -Force
Copy-Item -LiteralPath $hwdUnitSource -Destination $hwdUnitTarget -Force
Copy-Item -LiteralPath $netdUnitSource -Destination $netdUnitTarget -Force
Copy-Item -LiteralPath $timedUnitSource -Destination $timedUnitTarget -Force
Copy-Item -LiteralPath $alarmdUnitSource -Destination $alarmdUnitTarget -Force
Copy-Item -LiteralPath $snmpdUnitSource -Destination $snmpdUnitTarget -Force
Copy-Item -LiteralPath $logdUnitSource -Destination $logdUnitTarget -Force
Copy-Item -LiteralPath $updatedUnitSource -Destination $updatedUnitTarget -Force
Copy-Item -LiteralPath $serialdUnitSource -Destination $serialdUnitTarget -Force
Copy-Item -LiteralPath $sessiondUnitSource -Destination $sessiondUnitTarget -Force
Copy-Item -LiteralPath $webUnitSource -Destination $webUnitTarget -Force
Copy-Item -LiteralPath $cloneDemoWebUnitSource -Destination $cloneDemoWebUnitTarget -Force
} else {
Copy-Item -LiteralPath $snmpdBuildrootEnvSource -Destination $snmpdEnvTarget -Force
Copy-Item -LiteralPath $buildrootSerialdSource -Destination $buildrootSerialdTarget -Force
Copy-Item -LiteralPath $buildrootHwdSource -Destination $buildrootHwdTarget -Force
Copy-Item -LiteralPath $buildrootNetdSource -Destination $buildrootNetdTarget -Force
Copy-Item -LiteralPath $buildrootTimedSource -Destination $buildrootTimedTarget -Force
Copy-Item -LiteralPath $buildrootAlarmdSource -Destination $buildrootAlarmdTarget -Force
Copy-Item -LiteralPath $buildrootSNMPDSource -Destination $buildrootSNMPDTarget -Force
Copy-Item -LiteralPath $buildrootLogdSource -Destination $buildrootLogdTarget -Force
Copy-Item -LiteralPath $buildrootUpdatedSource -Destination $buildrootUpdatedTarget -Force
Copy-Item -LiteralPath $buildrootAPISource -Destination $buildrootAPITarget -Force
Copy-Item -LiteralPath $buildrootAccessdSource -Destination $buildrootAccessdTarget -Force
Copy-Item -LiteralPath $buildrootSessiondSource -Destination $buildrootSessiondTarget -Force
Copy-Item -LiteralPath $buildrootWebSource -Destination $buildrootWebTarget -Force
Copy-Item -LiteralPath $buildrootCloneDemoWebSource -Destination $buildrootCloneDemoWebTarget -Force
Copy-Item -LiteralPath $buildrootUdevSource -Destination $buildrootUdevTarget -Force
}
Assert-FileNonEmpty $binTarget
Assert-FileNonEmpty $accessdBinTarget
Assert-FileNonEmpty $hwdBinTarget
Assert-FileNonEmpty $netdBinTarget
Assert-FileNonEmpty $timedBinTarget
Assert-FileNonEmpty $alarmdBinTarget
Assert-FileNonEmpty $snmpdBinTarget
Assert-FileNonEmpty $logdBinTarget
Assert-FileNonEmpty $updatedBinTarget
Assert-FileNonEmpty $serialdBinTarget
Assert-FileNonEmpty $sessiondBinTarget
Assert-FileNonEmpty $webBinTarget
Assert-FileNonEmpty $cloneDemoWebBinTarget
Assert-FileNonEmpty $serialCheckBinTarget
Assert-FileNonEmpty $serialLineCheckBinTarget
Assert-FileNonEmpty $envTarget
Assert-FileNonEmpty $accessdEnvTarget
Assert-FileNonEmpty $hwdEnvTarget
Assert-FileNonEmpty $netdEnvTarget
Assert-FileNonEmpty $timedEnvTarget
Assert-FileNonEmpty $alarmdEnvTarget
Assert-FileNonEmpty $snmpdEnvTarget
Assert-FileNonEmpty $logdEnvTarget
Assert-FileNonEmpty $updatedEnvTarget
Assert-FileNonEmpty $serialdEnvTarget
Assert-FileNonEmpty $sessiondEnvTarget
Assert-FileNonEmpty $webEnvTarget
Assert-FileNonEmpty $cloneDemoWebEnvTarget
Assert-FileNonEmpty (Join-Path $webRootTarget "index.html")
Assert-FileNonEmpty (Join-Path $webRootTarget "src\direct-stream.js")
Assert-FileNonEmpty $serialMapTarget
Assert-FileNonEmpty $updateTrustReadmeTarget
foreach ($anchorTarget in $updateTrustAnchorTargets) {
Assert-FileNonEmpty $anchorTarget
}
if ($InitSystem -eq "systemd") {
Assert-FileNonEmpty $unitTarget
Assert-FileNonEmpty $accessdUnitTarget
Assert-FileNonEmpty $hwdUnitTarget
Assert-FileNonEmpty $netdUnitTarget
Assert-FileNonEmpty $timedUnitTarget
Assert-FileNonEmpty $alarmdUnitTarget
Assert-FileNonEmpty $snmpdUnitTarget
Assert-FileNonEmpty $logdUnitTarget
Assert-FileNonEmpty $updatedUnitTarget
Assert-FileNonEmpty $serialdUnitTarget
Assert-FileNonEmpty $sessiondUnitTarget
Assert-FileNonEmpty $webUnitTarget
Assert-FileNonEmpty $cloneDemoWebUnitTarget
} else {
Assert-FileNonEmpty $buildrootSerialdTarget
Assert-FileNonEmpty $buildrootHwdTarget
Assert-FileNonEmpty $buildrootNetdTarget
Assert-FileNonEmpty $buildrootTimedTarget
Assert-FileNonEmpty $buildrootAlarmdTarget
Assert-FileNonEmpty $buildrootSNMPDTarget
Assert-FileNonEmpty $buildrootLogdTarget
Assert-FileNonEmpty $buildrootUpdatedTarget
Assert-FileNonEmpty $buildrootAPITarget
Assert-FileNonEmpty $buildrootAccessdTarget
Assert-FileNonEmpty $buildrootSessiondTarget
Assert-FileNonEmpty $buildrootWebTarget
Assert-FileNonEmpty $buildrootCloneDemoWebTarget
Assert-FileNonEmpty $buildrootUdevTarget
}
$envText = Get-Content -Raw -LiteralPath $envTarget
$accessdEnvText = Get-Content -Raw -LiteralPath $accessdEnvTarget
$hwdEnvText = Get-Content -Raw -LiteralPath $hwdEnvTarget
$netdEnvText = Get-Content -Raw -LiteralPath $netdEnvTarget
$timedEnvText = Get-Content -Raw -LiteralPath $timedEnvTarget
$alarmdEnvText = Get-Content -Raw -LiteralPath $alarmdEnvTarget
$snmpdEnvText = Get-Content -Raw -LiteralPath $snmpdEnvTarget
$logdEnvText = Get-Content -Raw -LiteralPath $logdEnvTarget
$updatedEnvText = Get-Content -Raw -LiteralPath $updatedEnvTarget
$serialdEnvText = Get-Content -Raw -LiteralPath $serialdEnvTarget
$sessiondEnvText = Get-Content -Raw -LiteralPath $sessiondEnvTarget
$webEnvText = Get-Content -Raw -LiteralPath $webEnvTarget
$cloneDemoWebEnvText = Get-Content -Raw -LiteralPath $cloneDemoWebEnvTarget
$serialMapText = Get-Content -Raw -LiteralPath $serialMapTarget
Assert-Contains $envText "TERA_API_LISTEN_ADDR=127.0.0.1:8082" "target API must listen only behind the same-origin gateway"
Assert-Contains $envText "TERA_API_GREEN_LED_SOCKET=/run/tera-hwd/green-leds.sock" "target env must configure the hardware LED socket"
Assert-Contains $envText "TERA_API_SERIAL_SOCKET=/run/tera-seriald/serial-ports.sock" "target env must configure the serial controller socket"
Assert-Contains $envText "TERA_API_TIME_SOCKET=/run/tera-timed/time.sock" "target env must configure the system time socket"
Assert-Contains $envText "TERA_API_NETWORK_SOCKET=/run/tera-netd/control.sock" "target env must configure the privileged network socket"
Assert-Contains $envText "TERA_API_MANAGEMENT_SOCKET=/run/tera-accessd/control.sock" "target env must configure the management access socket"
Assert-Contains $accessdEnvText "TERA_ACCESSD_BIND_HOST=0.0.0.0" "management access listeners must bind the target interfaces"
Assert-Contains $envText "TERA_API_TLS_STORE=/var/lib/tera-api/tls/library" "TLS inventory must stay in protected API service state"
Assert-Contains $envText "TERA_API_TLS_ACTIVE_KEY=/var/lib/tera-api/tls/active.key" "active TLS private key must stay in protected API service state"
Assert-Contains $accessdEnvText "TERA_ACCESSD_TLS_KEY=/var/lib/tera-api/tls/active.key" "management listener must use the API-managed active TLS private key"
Assert-Contains $envText "TERA_API_ALARM_SOCKET=/run/tera-alarmd/alarm-delivery.sock" "target env must configure the alarm delivery socket"
Assert-Contains $envText "TERA_API_LOG_SOCKET=/run/tera-logd/log-store.sock" "target env must configure the authoritative log socket"
Assert-Contains $envText "TERA_API_LOG_SCHEDULER_ENABLED=true" "target env must enable configured status-event scheduling"
Assert-Contains $envText "TERA_API_LOG_ETHERNET_INTERFACES=Interface-1=eth0,Interface-2=eth1" "target env must bind logical Ethernet identities before reading carrier"
Assert-Contains $envText "TERA_API_UPDATE_SOCKET=/run/tera-updated/control.sock" "target env must configure the protected update socket"
Assert-Contains $envText "TERA_API_UPDATE_MAX_BYTES=1342177280" "target API must enforce the update package maximum"
Assert-Contains $envText "TERA_API_FIRMWARE_TIMEOUT_SECONDS=1800" "target API must use a route-specific bounded firmware timeout"
if ($envText.Contains("TERA_API_READ_TIMEOUT_SECONDS=1800") -or $envText.Contains("TERA_API_WRITE_TIMEOUT_SECONDS=1800")) {
throw "firmware upload must not expand the global API read/write timeouts"
}
Assert-Contains $envText "TERA_API_DIAGNOSTICS_ENABLED=true" "target env must enable the bounded diagnostics executor"
Assert-Contains $envText "TERA_API_DIAGNOSTICS_PING_PATH=/bin/ping" "target env must use a fixed ping executable"
Assert-Contains $envText "TERA_API_DIAGNOSTICS_WORKERS=2" "target diagnostics workers must remain bounded"
Assert-Contains $envText "TERA_API_DIAGNOSTICS_QUEUE=16" "target diagnostics queue must remain bounded"
Assert-Contains $envText "TERA_API_SUPPORT_BUNDLE_DIR=/var/lib/tera-api/support-bundles" "target support bundles must use the protected API state directory"
Assert-Contains $envText "TERA_API_SUPPORT_BUNDLE_MAX_ARTIFACTS=4" "target support bundle retention count must remain bounded"
Assert-Contains $envText "TERA_API_SUPPORT_BUNDLE_MAX_BYTES=8388608" "target support bundle artifact size must remain bounded"
Assert-Contains $envText "TERA_API_SUPPORT_BUNDLE_MAX_SECTION_BYTES=2097152" "target support bundle section size must remain bounded"
Assert-Contains $envText "TERA_API_AUTH_STORE=/var/lib/tera-api/auth.json" "target local users must persist across service restarts"
Assert-Contains $envText "TERA_API_SECRET_STORE=/var/lib/tera-api/secrets.json" "protected integration secrets must use the service-owned store path"
Assert-Contains $envText "TERA_API_MIN_PASSWORD_LENGTH=8" "target local password policy must enforce the approved eight-character minimum"
Assert-Contains $envText "TERA_API_PASSWORD_KDF=argon2id" "target env must default to Argon2id"
Assert-Contains $envText "TERA_API_ALLOW_MOCK_ACTORS=false" "target env must disable mock actors"
Assert-Contains $hwdEnvText "TERA_HWD_GREEN_LED_SYSFS_ROOT=/sys/class/leds" "hardware env must use kernel gpio-leds entries"
Assert-Contains $hwdEnvText "TERA_HWD_RESET_BUTTON_VALUE_PATH=/sys/class/gpio/manual_KeyReset/value" "hardware env must bind the active-low SW2 reset input"
Assert-Contains $envText "TERA_API_RESET_BUTTON_POLL_MILLISECONDS=100" "API env must poll the SW2 reset input promptly"
Assert-Contains $envText "TERA_API_RESET_BUTTON_HOLD_MILLISECONDS=1000" "API env must debounce SW2 before resetting settings"
Assert-Contains $netdEnvText "TERA_NETD_INTERFACES=/etc/network/interfaces" "network adapter must own the target interface file"
Assert-Contains $netdEnvText "TERA_NETD_PENDING=/var/lib/tera-network/pending.json" "network adapter must persist rollback state"
Assert-Contains $timedEnvText "TERA_TIMED_ZONEINFO_ROOT=/usr/share/zoneinfo" "time adapter must use the target zoneinfo database"
Assert-Contains $timedEnvText "TERA_TIMED_LOCALTIME_PATH=/etc/localtime" "time adapter must own only the product localtime path"
Assert-Contains $alarmdEnvText "TERA_ALARMD_STATE=/var/lib/tera-alarmd/deliveries.json" "alarm delivery retries must use the protected service state path"
Assert-Contains $alarmdEnvText "TERA_ALARMD_MAX_JOBS=256" "alarm delivery history and queue must remain bounded"
Assert-Contains $envText "TERA_API_SNMP_SOCKET=/run/tera-snmpd/control.sock" "API must use the privileged SNMP control socket"
Assert-Contains $snmpdEnvText "TERA_SNMPD_CONFIG=/etc/snmp/snmpd.conf" "SNMP adapter must use the Net-SNMP configuration path"
Assert-Contains $snmpdEnvText "TERA_SNMPD_PERSISTENT_CONFIG=/var/lib/net-snmp/snmpd.conf" "SNMPv3 bootstrap credentials must use the protected Net-SNMP state path"
Assert-Contains $logdEnvText "TERA_LOGD_ROOT=/userdata/log" "log service must use only the userdata log root"
Assert-Contains $logdEnvText "TERA_LOGD_STORAGE_POLICY=/userdata/storage-layout.json" "log service must consume the shared capacity policy"
Assert-Contains $logdEnvText "TERA_LOGD_SEGMENT_BYTES=4194304" "log service closed segments must remain bounded"
Assert-Contains $updatedEnvText "TERA_UPDATED_STORAGE_POLICY=/userdata/storage-layout.json" "updater must consume the shared capacity policy"
Assert-Contains $updatedEnvText "TERA_UPDATED_TRUST_DIR=/etc/tera/update-trust" "updater trust anchors must remain outside userdata"
Assert-Contains $updatedEnvText "TERA_UPDATED_PROFILE=fet3568j-c" "updater must bind packages to the target profile"
Assert-Contains $updatedEnvText "TERA_UPDATED_RECOVERY_APPLY_ENABLED=false" "recovery Apply must remain default-off until target evidence promotion"
Assert-Contains $updatedEnvText "TERA_UPDATED_RECOVERY_GATE=/etc/tera/recovery-apply-enabled.json" "recovery Apply must require the target evidence gate"
Assert-Contains $updatedEnvText "TERA_UPDATED_MISC_DEVICE=/dev/mmcblk0p2" "updater must bind the BCB request to the fixed misc partition"
Assert-Contains $serialdEnvText "TERA_SERIALD_MAPPING=/etc/tera/serial-map.json" "serial controller must require an explicit mapping artifact"
Assert-Contains $serialdEnvText "TERA_SERIALD_STREAM_SOCKET=/run/tera-seriald/serial-stream.sock" "serial controller must configure the separate data-plane socket"
Assert-Contains $serialdEnvText "TERA_SERIALD_EVENT_QUEUE=1024" "serial metadata observation must use a bounded queue"
Assert-Contains $serialdEnvText "TERA_SERIALD_REPORT_INTERVAL_SECONDS=10" "serial metadata observation must use a bounded report interval"
Assert-Contains $serialdEnvText "TERA_SERIALD_LOG_SOCKET=/run/tera-logd/log-store.sock" "serial payload capture must use the authoritative bounded log service"
Assert-Contains $envText "TERA_API_DIRECT_TICKET_SOCKET=/run/tera-api/direct-tickets.sock" "API must configure the Direct Mode ticket socket"
Assert-Contains $envText "TERA_API_SESSIOND_STATUS_URL=http://127.0.0.1:8081/readyz" "API must read actual session runtime health over loopback"
Assert-Contains $envText "TERA_API_SERIAL_RECONCILE_INTERVAL_SECONDS=30" "API must repair serial profiles after seriald restarts"
Assert-Contains $sessiondEnvText "TERA_SESSIOND_TICKET_SOCKET=/run/tera-api/direct-tickets.sock" "session broker must consume API tickets locally"
Assert-Contains $sessiondEnvText "TERA_SESSIOND_SERIAL_STREAM_SOCKET=/run/tera-seriald/serial-stream.sock" "session broker must use the serial data-plane socket"
Assert-Contains $webEnvText "TERA_WEB_LISTEN_ADDR=0.0.0.0:8080" "same-origin gateway must own the public listener"
Assert-Contains $webEnvText "TERA_WEB_API_UPSTREAM=http://127.0.0.1:8082" "same-origin gateway must route REST to loopback tera-api"
Assert-Contains $webEnvText "TERA_WEB_SESSION_UPSTREAM=http://127.0.0.1:8081" "same-origin gateway must route streams to loopback tera-sessiond"
if ($webEnvText -match "(?m)^PORT=" -or $webEnvText -match "(?m)^TERACLONE_DB_PATH=") {
throw "static tera-web env must not contain Clone Demo runtime settings"
}
Assert-Contains $cloneDemoWebEnvText "PORT=8083" "Clone Demo web must use its dedicated public port"
Assert-Contains $cloneDemoWebEnvText "TERACLONE_API_UPSTREAM=http://127.0.0.1:8082" "Clone Demo web must route REST to loopback tera-api"
Assert-Contains $cloneDemoWebEnvText "TERACLONE_SESSION_UPSTREAM=http://127.0.0.1:8081" "Clone Demo web must route streams to loopback tera-sessiond"
Assert-Contains $cloneDemoWebEnvText "TERACLONE_DB_PATH=/var/lib/tera-clone-demo-web/teraclone.db" "Clone Demo web must use isolated state"
$serialMap = $serialMapText | ConvertFrom-Json
if ($serialMap.format_version -ne 1 -or $serialMap.hardware_profile -ne "fet3568j-c" -or -not $serialMap.validated) {
throw "serial mapping metadata is invalid"
}
if ($serialMap.channels.Count -ne 48) {
throw "serial mapping must contain exactly 48 channels"
}
for ($index = 1; $index -le 48; $index++) {
$entry = $serialMap.channels[$index - 1]
$expectedChannel = "CH{0:d2}" -f $index
$expectedTTY = if ($index -le 24) { $index + 3 } else { $index + 7 }
$expectedDevice = "/dev/ttyWCH$expectedTTY"
$expectedBDF = if ($index -le 24) { "0000:01:00.0" } else { "0002:21:00.0" }
if ($entry.channel -ne $expectedChannel -or $entry.device -ne $expectedDevice -or $entry.pci_bdf -ne $expectedBDF) {
throw "serial mapping entry $expectedChannel does not match the canonical 24+24 layout"
}
}
if ($InitSystem -eq "systemd") {
$unitText = Get-Content -Raw -LiteralPath $unitTarget
$accessdUnitText = Get-Content -Raw -LiteralPath $accessdUnitTarget
$hwdUnitText = Get-Content -Raw -LiteralPath $hwdUnitTarget
$netdUnitText = Get-Content -Raw -LiteralPath $netdUnitTarget
$timedUnitText = Get-Content -Raw -LiteralPath $timedUnitTarget
$alarmdUnitText = Get-Content -Raw -LiteralPath $alarmdUnitTarget
$snmpdUnitText = Get-Content -Raw -LiteralPath $snmpdUnitTarget
$logdUnitText = Get-Content -Raw -LiteralPath $logdUnitTarget
$updatedUnitText = Get-Content -Raw -LiteralPath $updatedUnitTarget
$serialdUnitText = Get-Content -Raw -LiteralPath $serialdUnitTarget
$sessiondUnitText = Get-Content -Raw -LiteralPath $sessiondUnitTarget
$webUnitText = Get-Content -Raw -LiteralPath $webUnitTarget
$cloneDemoWebUnitText = Get-Content -Raw -LiteralPath $cloneDemoWebUnitTarget
Assert-Contains $unitText "ExecStart=/usr/bin/tera-api" "systemd unit must execute the installed API binary"
Assert-Contains $unitText "EnvironmentFile=-/etc/tera/tera-api.env" "systemd unit must load the target environment file"
Assert-Contains $unitText "User=tera" "systemd unit must run tera-api as the product service user"
Assert-Contains $unitText "NoNewPrivileges=true" "systemd unit must enable basic privilege hardening"
Assert-Contains $unitText "UMask=0007" "API ticket socket must remain group-restricted"
Assert-Contains $unitText "Wants=network-online.target tera-hwd.service tera-seriald.service tera-netd.service tera-logd.service tera-timed.service tera-alarmd.service tera-snmpd.service tera-updated.service tera-accessd.service" "tera-api must request its local service adapters"
Assert-Contains $accessdUnitText "ExecStart=/usr/bin/tera-accessd" "management access unit must execute the bounded listener service"
Assert-Contains $accessdUnitText "CapabilityBoundingSet=CAP_NET_BIND_SERVICE" "management access unit must retain only privileged-port binding capability"
Assert-Contains $accessdUnitText "ReadWritePaths=/run/tera-accessd /var/lib/tera-api/tls" "management access unit writes must stay in declared runtime and TLS state paths"
Assert-Contains $hwdUnitText "ExecStart=/usr/bin/tera-hwd" "hardware unit must execute the installed helper"
Assert-Contains $hwdUnitText "User=root" "hardware unit must own privileged gpio-led writes"
Assert-Contains $hwdUnitText "Group=tera" "hardware unit socket must be accessible to tera-api"
Assert-Contains $hwdUnitText "NoNewPrivileges=true" "hardware unit must enable basic privilege hardening"
Assert-Contains $hwdUnitText "ReadWritePaths=/run/tera-hwd /sys/class/leds" "hardware unit must only write its socket and gpio-led entries"
Assert-Contains $netdUnitText "ExecStart=/usr/bin/tera-netd" "network unit must execute the privileged adapter"
Assert-Contains $netdUnitText "CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW" "network unit must retain only network administration capabilities"
Assert-Contains $netdUnitText "ReadWritePaths=/run/tera-netd /var/lib/tera-network /etc/network" "network unit must restrict declared write paths"
Assert-Contains $timedUnitText "ExecStart=/usr/bin/tera-timed" "time unit must execute the installed helper"
Assert-Contains $timedUnitText "User=root" "time unit must own privileged clock changes"
Assert-Contains $timedUnitText "Group=tera" "time unit socket must be accessible to tera-api"
Assert-Contains $timedUnitText "CapabilityBoundingSet=CAP_SYS_TIME" "time unit must retain only the system-time capability"
Assert-Contains $timedUnitText "ReadWritePaths=/run/tera-timed /etc/localtime" "time unit must restrict declared write paths"
Assert-Contains $alarmdUnitText "ExecStart=/usr/bin/tera-alarmd" "alarm unit must execute the bounded delivery service"
Assert-Contains $alarmdUnitText "User=tera" "alarm delivery must run without root privileges"
Assert-Contains $alarmdUnitText "ReadWritePaths=/var/lib/tera-alarmd /run/tera-alarmd" "alarm unit must restrict declared write paths"
Assert-Contains $snmpdUnitText "ExecStart=/usr/bin/tera-snmpd" "SNMP unit must execute the privileged adapter"
Assert-Contains $snmpdUnitText "User=root" "SNMP adapter must own protected Net-SNMP configuration"
Assert-Contains $snmpdUnitText "ReadWritePaths=/run/tera-snmpd /etc/snmp /var/lib/net-snmp" "SNMP adapter writes must stay in declared paths"
Assert-Contains $logdUnitText "ExecStart=/usr/bin/tera-logd" "log unit must execute the authoritative storage service"
Assert-Contains $logdUnitText "User=tera-logd" "log unit must use the dedicated userdata owner"
Assert-Contains $logdUnitText "ReadWritePaths=/userdata/log /run/tera-logd" "log unit must never spill into rootfs"
Assert-Contains $updatedUnitText "ExecStart=/usr/bin/tera-updated" "update unit must execute the signed package validator"
Assert-Contains $updatedUnitText "User=tera-updated" "update unit must use the dedicated storage owner"
Assert-Contains $updatedUnitText "ReadWritePaths=/userdata/update /run/tera-updated" "update unit must restrict write paths"
Assert-Contains $serialdUnitText "ExecStart=/usr/bin/tera-seriald" "serial unit must execute the installed controller"
Assert-Contains $serialdUnitText "User=tera" "serial controller must run without root privileges"
Assert-Contains $serialdUnitText "DevicePolicy=closed" "serial controller must close unrelated devices"
Assert-Contains $serialdUnitText "DeviceAllow=/dev/ttyWCH* rw" "serial controller must only access WCH tty devices"
Assert-Contains $serialdUnitText "UMask=0007" "serial controller sockets must remain group-restricted"
Assert-Contains $serialdUnitText "Wants=tera-logd.service" "serial payload capture must request the authoritative log service"
Assert-Contains $sessiondUnitText "ExecStart=/usr/bin/tera-sessiond" "session unit must execute the bounded broker"
Assert-Contains $sessiondUnitText "User=tera" "session broker must run without root privileges"
Assert-Contains $sessiondUnitText "UMask=0007" "session broker local IPC must remain group-restricted"
Assert-Contains $webUnitText "ExecStart=/usr/bin/tera-web" "web unit must execute the static same-origin gateway"
Assert-Contains $webUnitText "ReadOnlyPaths=/usr/share/tera-web" "web unit must treat static assets as read-only"
Assert-Contains $webUnitText "UMask=0027" "static gateway files must not become world-writable"
Assert-Contains $cloneDemoWebUnitText "ExecStart=/usr/bin/tera-clone-demo-web" "Clone Demo unit must execute its dedicated binary"
Assert-Contains $cloneDemoWebUnitText "EnvironmentFile=-/etc/tera/tera-clone-demo-web.env" "Clone Demo unit must load its dedicated environment"
Assert-Contains $cloneDemoWebUnitText "ReadWritePaths=/var/lib/tera-clone-demo-web" "Clone Demo unit must isolate its writable state"
} else {
$buildrootSerialdText = Get-Content -Raw -LiteralPath $buildrootSerialdTarget
$buildrootHwdText = Get-Content -Raw -LiteralPath $buildrootHwdTarget
$buildrootNetdText = Get-Content -Raw -LiteralPath $buildrootNetdTarget
$buildrootTimedText = Get-Content -Raw -LiteralPath $buildrootTimedTarget
$buildrootAlarmdText = Get-Content -Raw -LiteralPath $buildrootAlarmdTarget
$buildrootSNMPDText = Get-Content -Raw -LiteralPath $buildrootSNMPDTarget
$buildrootLogdText = Get-Content -Raw -LiteralPath $buildrootLogdTarget
$buildrootUpdatedText = Get-Content -Raw -LiteralPath $buildrootUpdatedTarget
$buildrootAPIText = Get-Content -Raw -LiteralPath $buildrootAPITarget
$buildrootAccessdText = Get-Content -Raw -LiteralPath $buildrootAccessdTarget
$buildrootSessiondText = Get-Content -Raw -LiteralPath $buildrootSessiondTarget
$buildrootWebText = Get-Content -Raw -LiteralPath $buildrootWebTarget
$buildrootCloneDemoWebText = Get-Content -Raw -LiteralPath $buildrootCloneDemoWebTarget
$buildrootUdevText = Get-Content -Raw -LiteralPath $buildrootUdevTarget
Assert-Contains $buildrootSerialdText "DAEMON=/usr/bin/tera-seriald" "Buildroot serial init must execute tera-seriald"
Assert-Contains $buildrootSerialdText "RUN_AS=tera:tera" "Buildroot serial init must drop privileges"
Assert-Contains $buildrootSerialdText "normalize_service_account_password" "Buildroot service account setup must avoid BusyBox bad-salt sentinels"
Assert-Contains $buildrootSerialdText 'mkpasswd -m sha-256' "Buildroot service account sentinel must use a supported non-plaintext hash"
Assert-Contains $buildrootSerialdText '-s /bin/false' "Buildroot tera service account must remain non-interactive"
Assert-Contains $buildrootSerialdText "start-stop-daemon" "Buildroot serial init must use BusyBox supervision"
Assert-Contains $buildrootHwdText "DAEMON=/usr/bin/tera-hwd" "Buildroot hardware init must execute tera-hwd"
Assert-Contains $buildrootHwdText "RUN_AS=root:tera" "Buildroot hardware init must retain root UID with only the shared service group"
Assert-Contains $buildrootHwdText "umask 0007" "Buildroot hardware socket must not be world accessible"
Assert-Contains $buildrootNetdText "DAEMON=/usr/bin/tera-netd" "Buildroot network init must execute tera-netd"
Assert-Contains $buildrootNetdText "RUN_AS=root:tera" "Buildroot network adapter must retain root UID with only the shared service group"
Assert-Contains $buildrootNetdText "chmod 0700 /var/lib/tera-network" "Buildroot rollback state must be root-only"
Assert-Contains $buildrootTimedText "DAEMON=/usr/bin/tera-timed" "Buildroot time init must execute tera-timed"
Assert-Contains $buildrootTimedText "chown root:tera /run/tera-timed" "Buildroot time socket directory must be group restricted"
Assert-Contains $buildrootTimedText "RUN_AS=root:tera" "Buildroot time adapter must retain root UID with only the shared service group"
Assert-Contains $buildrootTimedText "umask 0007" "Buildroot time socket must not be world accessible"
Assert-Contains $buildrootAlarmdText "DAEMON=/usr/bin/tera-alarmd" "Buildroot alarm init must execute tera-alarmd"
Assert-Contains $buildrootAlarmdText "start-stop-daemon" "Buildroot alarm init must use BusyBox supervision"
Assert-Contains $buildrootSNMPDText "DAEMON=/usr/bin/tera-snmpd" "Buildroot SNMP init must execute tera-snmpd"
Assert-Contains $buildrootSNMPDText "RUN_AS=root:tera" "Buildroot SNMP adapter must retain root UID with the shared service group"
Assert-Contains $buildrootSNMPDText "chmod 0700 /var/lib/net-snmp" "Buildroot SNMPv3 state must remain root-only"
Assert-Contains $buildrootLogdText "DAEMON=/usr/bin/tera-logd" "Buildroot log init must execute tera-logd"
Assert-Contains $buildrootLogdText "RUN_AS=tera-logd:tera" "Buildroot log init must isolate storage ownership while sharing the API socket group"
Assert-Contains $buildrootLogdText "missing /userdata/log" "Buildroot log init must fail closed without userdata logging"
Assert-Contains $buildrootUpdatedText "DAEMON=/usr/bin/tera-updated" "Buildroot update init must execute tera-updated"
Assert-Contains $buildrootUpdatedText "RUN_AS=tera-updated:tera" "Buildroot update init must isolate storage ownership while sharing the API socket group"
Assert-Contains $buildrootUpdatedText "/usr/libexec/tera-storage-init --check" "Buildroot update init must fail closed when storage policy is invalid"
Assert-Contains $buildrootAPIText "DAEMON=/usr/bin/tera-api" "Buildroot API init must execute tera-api"
Assert-Contains $buildrootAPIText "RUN_AS=tera:tera" "Buildroot API init must drop privileges"
Assert-Contains $buildrootAPIText "chown -R tera:tera /var/lib/tera-api /var/log/tera" "Buildroot API init must repair restored state ownership"
Assert-Contains $buildrootAPIText 'wait_for_socket "$TERA_API_LOG_SOCKET"' "Buildroot API init must wait for the authoritative log service before startup"
Assert-Contains $buildrootAccessdText "DAEMON=/usr/bin/tera-accessd" "Buildroot management access init must execute tera-accessd"
Assert-Contains $buildrootAccessdText "chown -R tera:tera /var/lib/tera-api/tls" "Buildroot TLS state must be owned by the restricted service account"
Assert-Contains $buildrootAccessdText "chmod 0700 /var/lib/tera-api/tls /var/lib/tera-api/tls/library" "Buildroot TLS private state must remain service-account-only"
Assert-Contains $buildrootAccessdText "chown root:tera /run/tera-accessd" "Buildroot management control directory must be group traversable"
Assert-Contains $buildrootSessiondText "DAEMON=/usr/bin/tera-sessiond" "Buildroot session init must execute tera-sessiond"
Assert-Contains $buildrootSessiondText "RUN_AS=tera:tera" "Buildroot session init must drop privileges"
Assert-Contains $buildrootWebText "DAEMON=/usr/bin/tera-web" "Buildroot web init must execute tera-web"
Assert-Contains $buildrootWebText "RUN_AS=tera:tera" "Buildroot web init must drop privileges"
Assert-Contains $buildrootCloneDemoWebText "DAEMON=/usr/bin/tera-clone-demo-web" "Buildroot Clone Demo init must execute its dedicated binary"
Assert-Contains $buildrootCloneDemoWebText "ENVFILE=/etc/tera/tera-clone-demo-web.env" "Buildroot Clone Demo init must load its dedicated environment"
Assert-Contains $buildrootCloneDemoWebText "STATE_DIR=/var/lib/tera-clone-demo-web" "Buildroot Clone Demo init must isolate its writable state"
Assert-Contains $buildrootUdevText 'KERNEL=="ttyWCH[0-9]*", GROUP="tera", MODE="0660"' "Buildroot udev rule must restrict WCH access to the tera group"
foreach ($initText in @($buildrootSerialdText, $buildrootHwdText, $buildrootNetdText, $buildrootLogdText, $buildrootTimedText, $buildrootAlarmdText, $buildrootSNMPDText, $buildrootUpdatedText, $buildrootAccessdText, $buildrootAPIText, $buildrootSessiondText, $buildrootWebText, $buildrootCloneDemoWebText)) {
Assert-Contains $initText 'rm -f "$PIDFILE"' "Buildroot service init must remove stale PID files before start"
}
}
if ($envText -match "(?im)^\s*TERA_API_CORS_ORIGINS\s*=\s*\*") {
throw "target env must not enable wildcard CORS"
}
Assert-NoSecretEnvironmentValues $envText
Assert-NoSecretEnvironmentValues $accessdEnvText
Assert-NoSecretEnvironmentValues $hwdEnvText
Assert-NoSecretEnvironmentValues $netdEnvText
Assert-NoSecretEnvironmentValues $timedEnvText
Assert-NoSecretEnvironmentValues $alarmdEnvText
Assert-NoSecretEnvironmentValues $snmpdEnvText
Assert-NoSecretEnvironmentValues $logdEnvText
Assert-NoSecretEnvironmentValues $updatedEnvText
Assert-NoSecretEnvironmentValues $serialdEnvText
Assert-NoSecretEnvironmentValues $sessiondEnvText
Assert-NoSecretEnvironmentValues $webEnvText
Assert-NoSecretEnvironmentValues $cloneDemoWebEnvText
$artifact = Get-Item -LiteralPath $binTarget
$accessdArtifact = Get-Item -LiteralPath $accessdBinTarget
$hwdArtifact = Get-Item -LiteralPath $hwdBinTarget
$netdArtifact = Get-Item -LiteralPath $netdBinTarget
$timedArtifact = Get-Item -LiteralPath $timedBinTarget
$alarmdArtifact = Get-Item -LiteralPath $alarmdBinTarget
$snmpdArtifact = Get-Item -LiteralPath $snmpdBinTarget
$logdArtifact = Get-Item -LiteralPath $logdBinTarget
$updatedArtifact = Get-Item -LiteralPath $updatedBinTarget
$serialdArtifact = Get-Item -LiteralPath $serialdBinTarget
$sessiondArtifact = Get-Item -LiteralPath $sessiondBinTarget
$webArtifact = Get-Item -LiteralPath $webBinTarget
$cloneDemoWebArtifact = Get-Item -LiteralPath $cloneDemoWebBinTarget
$serialCheckArtifact = Get-Item -LiteralPath $serialCheckBinTarget
$serialLineCheckArtifact = Get-Item -LiteralPath $serialLineCheckBinTarget
if ($createdTempRoot -and -not $KeepStaging) {
Write-Host "[check-target-rootfs-staging] Verified temporary tera-api $InitSystem rootfs staging at $StagingRoot"
} else {
Write-Host "[check-target-rootfs-staging] Staged tera-api $InitSystem rootfs at $StagingRoot"
}
Write-Host "[check-target-rootfs-staging] Binary size: $($artifact.Length) bytes"
Write-Host "[check-target-rootfs-staging] Management access service size: $($accessdArtifact.Length) bytes"
Write-Host "[check-target-rootfs-staging] Hardware helper size: $($hwdArtifact.Length) bytes"
Write-Host "[check-target-rootfs-staging] Network helper size: $($netdArtifact.Length) bytes"
Write-Host "[check-target-rootfs-staging] System time helper size: $($timedArtifact.Length) bytes"
Write-Host "[check-target-rootfs-staging] Alarm delivery service size: $($alarmdArtifact.Length) bytes"
Write-Host "[check-target-rootfs-staging] SNMP agent adapter size: $($snmpdArtifact.Length) bytes"
Write-Host "[check-target-rootfs-staging] Authoritative log service size: $($logdArtifact.Length) bytes"
Write-Host "[check-target-rootfs-staging] Firmware validation service size: $($updatedArtifact.Length) bytes"
Write-Host "[check-target-rootfs-staging] Serial controller size: $($serialdArtifact.Length) bytes"
Write-Host "[check-target-rootfs-staging] Session broker size: $($sessiondArtifact.Length) bytes"
Write-Host "[check-target-rootfs-staging] Static gateway size: $($webArtifact.Length) bytes"
Write-Host "[check-target-rootfs-staging] Clone Demo web size: $($cloneDemoWebArtifact.Length) bytes"
Write-Host "[check-target-rootfs-staging] Read-only serial checker size: $($serialCheckArtifact.Length) bytes"
Write-Host "[check-target-rootfs-staging] Restoring serial line checker size: $($serialLineCheckArtifact.Length) bytes"
} finally {
if ($createdTempRoot -and -not $KeepStaging -and (Test-Path -LiteralPath $StagingRoot)) {
Remove-Item -LiteralPath $StagingRoot -Recurse -Force
}
}