mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 12:26:38 -06:00
522 lines
23 KiB
YAML
522 lines
23 KiB
YAML
name: Windows Node Release
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
tag:
|
|
description: Existing OpenClaw release tag to receive Windows Hub installers
|
|
required: true
|
|
type: string
|
|
windows_node_tag:
|
|
description: Exact openclaw-windows-node release tag to promote
|
|
required: true
|
|
type: string
|
|
expected_installer_digests:
|
|
description: Compact JSON map of installer asset names to pinned source sha256 digests
|
|
required: true
|
|
type: string
|
|
release_publish_run_id:
|
|
description: OpenClaw Release Publish run that approved this promotion
|
|
required: true
|
|
type: string
|
|
release_publish_run_attempt:
|
|
description: Exact approving OpenClaw Release Publish run attempt
|
|
required: true
|
|
type: string
|
|
release_tooling_ref:
|
|
description: Exact ref name of the approving release tooling
|
|
required: true
|
|
type: string
|
|
release_tooling_full_ref:
|
|
description: Exact full ref of the approving release tooling
|
|
required: true
|
|
type: string
|
|
release_tooling_sha:
|
|
description: Exact SHA of the approving release tooling
|
|
required: true
|
|
type: string
|
|
release_target_sha:
|
|
description: Exact OpenClaw release candidate SHA
|
|
required: true
|
|
type: string
|
|
direct_release_recovery:
|
|
description: Reuse the exact original approval after its parent run completed
|
|
required: true
|
|
default: false
|
|
type: boolean
|
|
|
|
permissions:
|
|
actions: read
|
|
attestations: read
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: windows-node-release-${{ inputs.tag }}
|
|
cancel-in-progress: false
|
|
|
|
env:
|
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
|
NODE_VERSION: "24.16.0"
|
|
|
|
jobs:
|
|
validate_signed_windows_installers:
|
|
name: Validate signed Windows installers
|
|
runs-on: windows-latest
|
|
timeout-minutes: 30
|
|
steps:
|
|
- name: Checkout exact trusted release tooling
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
ref: ${{ inputs.release_tooling_sha }}
|
|
fetch-depth: 1
|
|
persist-credentials: false
|
|
sparse-checkout: |
|
|
scripts/lib/record-shared.mjs
|
|
scripts/release-tooling-identity.mjs
|
|
scripts/validate-release-publish-approval.mjs
|
|
sparse-checkout-cone-mode: false
|
|
|
|
- name: Download parent Windows release approval
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
name: windows-release-approval-${{ inputs.release_publish_run_id }}-${{ inputs.release_publish_run_attempt }}
|
|
path: ${{ runner.temp }}/windows-release-approval
|
|
repository: ${{ github.repository }}
|
|
run-id: ${{ inputs.release_publish_run_id }}
|
|
github-token: ${{ github.token }}
|
|
|
|
- name: Validate immutable release approval
|
|
shell: pwsh
|
|
env:
|
|
APPROVAL_PATH: ${{ runner.temp }}/windows-release-approval/approval.json
|
|
CHILD_WORKFLOW_FULL_REF: ${{ github.ref }}
|
|
CHILD_WORKFLOW_REF: ${{ github.ref_name }}
|
|
CHILD_WORKFLOW_SHA: ${{ github.workflow_sha }}
|
|
DIRECT_RELEASE_RECOVERY: ${{ inputs.direct_release_recovery && 'true' || 'false' }}
|
|
EXPECTED_RUN_ATTEMPT: ${{ inputs.release_publish_run_attempt }}
|
|
EXPECTED_WORKFLOW_BRANCH: ${{ inputs.release_tooling_ref }}
|
|
EXPECTED_WORKFLOW_FULL_REF: ${{ inputs.release_tooling_full_ref }}
|
|
EXPECTED_WORKFLOW_SHA: ${{ inputs.release_tooling_sha }}
|
|
GH_TOKEN: ${{ github.token }}
|
|
RELEASE_APPROVAL_KIND: windows-release
|
|
RELEASE_PUBLISH_RUN_ID: ${{ inputs.release_publish_run_id }}
|
|
RELEASE_TAG: ${{ inputs.tag }}
|
|
RELEASE_TARGET_SHA: ${{ inputs.release_target_sha }}
|
|
RELEASE_TOOLING_FULL_REF: ${{ inputs.release_tooling_full_ref }}
|
|
RELEASE_TOOLING_REF: ${{ inputs.release_tooling_ref }}
|
|
RELEASE_TOOLING_SHA: ${{ inputs.release_tooling_sha }}
|
|
WINDOWS_NODE_INSTALLER_DIGESTS: ${{ inputs.expected_installer_digests }}
|
|
WINDOWS_NODE_TAG: ${{ inputs.windows_node_tag }}
|
|
run: |
|
|
$ErrorActionPreference = "Stop"
|
|
if ($env:GITHUB_ACTOR -ne "github-actions[bot]" -and $env:DIRECT_RELEASE_RECOVERY -ne "true") {
|
|
throw "Manual Windows release recovery must explicitly set direct_release_recovery=true."
|
|
}
|
|
if (
|
|
$env:CHILD_WORKFLOW_REF -ne $env:RELEASE_TOOLING_REF -or
|
|
$env:CHILD_WORKFLOW_FULL_REF -ne $env:RELEASE_TOOLING_FULL_REF -or
|
|
$env:CHILD_WORKFLOW_SHA -ne $env:RELEASE_TOOLING_SHA
|
|
) {
|
|
throw "Windows release workflow execution does not match the attested tooling tuple."
|
|
}
|
|
$verifyArgs = @(
|
|
"verify",
|
|
"--repository", $env:GITHUB_REPOSITORY,
|
|
"--workflow-ref", $env:RELEASE_TOOLING_REF,
|
|
"--workflow-full-ref", $env:RELEASE_TOOLING_FULL_REF,
|
|
"--workflow-sha", $env:RELEASE_TOOLING_SHA
|
|
)
|
|
if (
|
|
$env:RELEASE_TOOLING_REF -ne "main" -and
|
|
$env:RELEASE_TOOLING_FULL_REF -notmatch '^refs/tags/release-publish/'
|
|
) {
|
|
$verifyArgs += "--allow-prevalidated-ref"
|
|
}
|
|
node scripts/release-tooling-identity.mjs @verifyArgs | Out-Null
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Windows release tooling identity is no longer live."
|
|
}
|
|
gh attestation verify $env:APPROVAL_PATH `
|
|
--repo $env:GITHUB_REPOSITORY `
|
|
--signer-workflow "$env:GITHUB_REPOSITORY/.github/workflows/openclaw-release-publish.yml" `
|
|
--source-ref $env:RELEASE_TOOLING_FULL_REF `
|
|
--source-digest $env:RELEASE_TOOLING_SHA `
|
|
--deny-self-hosted-runners
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Windows release approval attestation verification failed."
|
|
}
|
|
gh api `
|
|
"repos/$env:GITHUB_REPOSITORY/actions/runs/$env:RELEASE_PUBLISH_RUN_ID/attempts/$env:EXPECTED_RUN_ATTEMPT" `
|
|
--jq '{workflowName: .name, headBranch: .head_branch, headSha: .head_sha, event, status, conclusion, url: .html_url, runAttempt: .run_attempt, repository: .repository.full_name, path}' |
|
|
node scripts/validate-release-publish-approval.mjs
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Windows release approval does not match the exact parent run."
|
|
}
|
|
|
|
- name: Validate inputs and download installers
|
|
shell: pwsh
|
|
env:
|
|
DIRECT_RELEASE_RECOVERY: ${{ inputs.direct_release_recovery && 'true' || 'false' }}
|
|
EXPECTED_INSTALLER_DIGESTS: ${{ inputs.expected_installer_digests }}
|
|
GH_TOKEN: ${{ github.token }}
|
|
RELEASE_TAG: ${{ inputs.tag }}
|
|
RELEASE_TARGET_SHA: ${{ inputs.release_target_sha }}
|
|
WINDOWS_NODE_TAG: ${{ inputs.windows_node_tag }}
|
|
run: |
|
|
$ErrorActionPreference = "Stop"
|
|
if ($env:RELEASE_TAG -notmatch '^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-(alpha|beta)\.[1-9][0-9]*)|(-[1-9][0-9]*))?$') {
|
|
throw "Invalid OpenClaw release tag: $env:RELEASE_TAG"
|
|
}
|
|
if ($env:RELEASE_TARGET_SHA -notmatch '^[a-f0-9]{40}$') {
|
|
throw "release_target_sha must be a full lowercase commit SHA."
|
|
}
|
|
if ($env:WINDOWS_NODE_TAG -notmatch '^v[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z]+([.-][0-9A-Za-z]+)*)?$') {
|
|
throw "windows_node_tag must be an explicit openclaw-windows-node release tag, not latest: $env:WINDOWS_NODE_TAG"
|
|
}
|
|
|
|
try {
|
|
$expectedDigests = $env:EXPECTED_INSTALLER_DIGESTS | ConvertFrom-Json -AsHashtable
|
|
} catch {
|
|
throw "expected_installer_digests must be a JSON object: $_"
|
|
}
|
|
$requiredInstallerNames = @(
|
|
"OpenClawCompanion-Setup-x64.exe",
|
|
"OpenClawCompanion-Setup-arm64.exe"
|
|
)
|
|
$allowedTargetCompanionAssetNames = @(
|
|
$requiredInstallerNames
|
|
"OpenClawCompanion-SHA256SUMS.txt"
|
|
)
|
|
if ($expectedDigests.Count -ne $requiredInstallerNames.Count) {
|
|
throw "expected_installer_digests must contain exactly the current installer asset contract."
|
|
}
|
|
foreach ($name in $requiredInstallerNames) {
|
|
$digest = [string]$expectedDigests[$name]
|
|
if ($digest -notmatch '^sha256:[a-f0-9]{64}$') {
|
|
throw "expected_installer_digests is missing a valid pinned digest for $name."
|
|
}
|
|
}
|
|
|
|
$targetSha = gh api "repos/$env:GITHUB_REPOSITORY/commits/$env:RELEASE_TAG" --jq .sha
|
|
if ($targetSha -ne $env:RELEASE_TARGET_SHA) {
|
|
throw "OpenClaw release tag no longer resolves to the approved candidate SHA."
|
|
}
|
|
$targetRelease = gh release view $env:RELEASE_TAG `
|
|
--repo $env:GITHUB_REPOSITORY `
|
|
--json tagName,isDraft,isPrerelease,assets,url |
|
|
ConvertFrom-Json
|
|
if ($targetRelease.tagName -ne $env:RELEASE_TAG) {
|
|
throw "OpenClaw release tag mismatch: expected $env:RELEASE_TAG, got $($targetRelease.tagName)"
|
|
}
|
|
if ($env:DIRECT_RELEASE_RECOVERY -ne "true" -and -not $targetRelease.isDraft) {
|
|
throw "Normal Windows promotion requires the target GitHub release to remain a draft."
|
|
}
|
|
$unexpectedTargetCompanionAssets = @(
|
|
$targetRelease.assets |
|
|
Where-Object {
|
|
$_.name.StartsWith("OpenClawCompanion-") -and
|
|
$_.name -notin $allowedTargetCompanionAssetNames
|
|
} |
|
|
ForEach-Object name |
|
|
Sort-Object
|
|
)
|
|
if ($unexpectedTargetCompanionAssets.Count -ne 0) {
|
|
throw "Target OpenClaw release contains unexpected OpenClawCompanion assets before upload: $($unexpectedTargetCompanionAssets -join ', ')"
|
|
}
|
|
|
|
$stableRelease = -not (
|
|
$env:RELEASE_TAG.Contains("-alpha.") -or
|
|
$env:RELEASE_TAG.Contains("-beta.")
|
|
)
|
|
$sourceRelease = gh release view $env:WINDOWS_NODE_TAG `
|
|
--repo openclaw/openclaw-windows-node `
|
|
--json tagName,isDraft,isPrerelease,assets,url |
|
|
ConvertFrom-Json
|
|
if ($sourceRelease.tagName -ne $env:WINDOWS_NODE_TAG) {
|
|
throw "Windows source release tag mismatch: expected $env:WINDOWS_NODE_TAG, got $($sourceRelease.tagName)"
|
|
}
|
|
if ($sourceRelease.isDraft) {
|
|
throw "Windows source release must be published: $($sourceRelease.url)"
|
|
}
|
|
if ($stableRelease -and $sourceRelease.isPrerelease) {
|
|
throw "Stable OpenClaw releases require a non-prerelease Windows source release: $($sourceRelease.url)"
|
|
}
|
|
foreach ($name in $requiredInstallerNames) {
|
|
$sourceAssets = @($sourceRelease.assets | Where-Object name -eq $name)
|
|
if ($sourceAssets.Count -ne 1) {
|
|
throw "Windows source release must contain exactly one required asset $name; found $($sourceAssets.Count)."
|
|
}
|
|
if ([string]$sourceAssets[0].digest -ne [string]$expectedDigests[$name]) {
|
|
throw "Windows source release asset digest does not match the pinned digest: $name"
|
|
}
|
|
}
|
|
|
|
New-Item -ItemType Directory -Force -Path dist | Out-Null
|
|
$downloadArgs = @(
|
|
$env:WINDOWS_NODE_TAG,
|
|
"--repo", "openclaw/openclaw-windows-node",
|
|
"--dir", "dist"
|
|
)
|
|
foreach ($name in $requiredInstallerNames) {
|
|
$downloadArgs += @("--pattern", $name)
|
|
}
|
|
gh release download @downloadArgs
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Failed to download Windows release assets from $env:WINDOWS_NODE_TAG."
|
|
}
|
|
foreach ($name in $requiredInstallerNames) {
|
|
$matches = @(Get-ChildItem -LiteralPath dist -File | Where-Object Name -eq $name)
|
|
if ($matches.Count -ne 1) {
|
|
throw "Expected exactly one Windows installer named '$name', found $($matches.Count)."
|
|
}
|
|
$expectedHash = ([string]$expectedDigests[$name]) -replace '^sha256:', ''
|
|
$actualHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $matches[0].FullName).Hash
|
|
if ($actualHash -ne $expectedHash) {
|
|
throw "Downloaded Windows source asset does not match pinned digest: $name"
|
|
}
|
|
}
|
|
|
|
- name: Verify Authenticode signatures
|
|
shell: pwsh
|
|
run: |
|
|
$ErrorActionPreference = "Stop"
|
|
$expectedSignerSubject = "CN=OpenClaw Foundation, O=OpenClaw Foundation, L=Mill Valley, S=California, C=US"
|
|
Get-ChildItem -LiteralPath dist -File | ForEach-Object {
|
|
$signature = Get-AuthenticodeSignature -LiteralPath $_.FullName
|
|
if ($signature.Status -ne "Valid") {
|
|
throw "$($_.Name) Authenticode signature was $($signature.Status)."
|
|
}
|
|
if (-not $signature.SignerCertificate) {
|
|
throw "$($_.Name) has no signer certificate."
|
|
}
|
|
if ($signature.SignerCertificate.Subject -ne $expectedSignerSubject) {
|
|
throw "$($_.Name) has unexpected signer subject $($signature.SignerCertificate.Subject)."
|
|
}
|
|
}
|
|
|
|
- name: Write SHA-256 manifest
|
|
shell: pwsh
|
|
run: |
|
|
Get-ChildItem -LiteralPath dist -File |
|
|
Sort-Object Name |
|
|
ForEach-Object {
|
|
$hash = Get-FileHash -Algorithm SHA256 -LiteralPath $_.FullName
|
|
"$($hash.Hash.ToLowerInvariant()) $($_.Name)"
|
|
} | Set-Content -Encoding utf8NoBOM -Path dist/OpenClawCompanion-SHA256SUMS.txt
|
|
|
|
- name: Upload validated Windows installers
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: windows-release-prepared-${{ github.run_id }}
|
|
path: dist
|
|
if-no-files-found: error
|
|
retention-days: 30
|
|
|
|
promote_signed_windows_installers:
|
|
name: Promote signed Windows installers
|
|
needs: [validate_signed_windows_installers]
|
|
runs-on: windows-latest
|
|
timeout-minutes: 30
|
|
environment: npm-release
|
|
permissions:
|
|
actions: read
|
|
attestations: read
|
|
contents: write
|
|
steps:
|
|
- name: Checkout exact trusted release tooling
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
ref: ${{ inputs.release_tooling_sha }}
|
|
fetch-depth: 1
|
|
persist-credentials: false
|
|
sparse-checkout: |
|
|
scripts/lib/record-shared.mjs
|
|
scripts/release-tooling-identity.mjs
|
|
scripts/validate-release-publish-approval.mjs
|
|
sparse-checkout-cone-mode: false
|
|
|
|
- name: Download parent Windows release approval
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
name: windows-release-approval-${{ inputs.release_publish_run_id }}-${{ inputs.release_publish_run_attempt }}
|
|
path: ${{ runner.temp }}/windows-release-approval
|
|
repository: ${{ github.repository }}
|
|
run-id: ${{ inputs.release_publish_run_id }}
|
|
github-token: ${{ github.token }}
|
|
|
|
- name: Download validated Windows installers
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
name: windows-release-prepared-${{ github.run_id }}
|
|
path: dist
|
|
|
|
- name: Upload to OpenClaw release
|
|
shell: pwsh
|
|
env:
|
|
APPROVAL_PATH: ${{ runner.temp }}/windows-release-approval/approval.json
|
|
DIRECT_RELEASE_RECOVERY: ${{ inputs.direct_release_recovery && 'true' || 'false' }}
|
|
EXPECTED_INSTALLER_DIGESTS: ${{ inputs.expected_installer_digests }}
|
|
EXPECTED_RUN_ATTEMPT: ${{ inputs.release_publish_run_attempt }}
|
|
EXPECTED_WORKFLOW_BRANCH: ${{ inputs.release_tooling_ref }}
|
|
EXPECTED_WORKFLOW_FULL_REF: ${{ inputs.release_tooling_full_ref }}
|
|
EXPECTED_WORKFLOW_SHA: ${{ inputs.release_tooling_sha }}
|
|
GH_TOKEN: ${{ github.token }}
|
|
RELEASE_APPROVAL_KIND: windows-release
|
|
RELEASE_PUBLISH_RUN_ID: ${{ inputs.release_publish_run_id }}
|
|
RELEASE_TAG: ${{ inputs.tag }}
|
|
RELEASE_TARGET_SHA: ${{ inputs.release_target_sha }}
|
|
RELEASE_TOOLING_FULL_REF: ${{ inputs.release_tooling_full_ref }}
|
|
RELEASE_TOOLING_REF: ${{ inputs.release_tooling_ref }}
|
|
RELEASE_TOOLING_SHA: ${{ inputs.release_tooling_sha }}
|
|
WINDOWS_NODE_INSTALLER_DIGESTS: ${{ inputs.expected_installer_digests }}
|
|
WINDOWS_NODE_TAG: ${{ inputs.windows_node_tag }}
|
|
run: |
|
|
$ErrorActionPreference = "Stop"
|
|
function Assert-ReleaseApproval {
|
|
$verifyArgs = @(
|
|
"verify",
|
|
"--repository", $env:GITHUB_REPOSITORY,
|
|
"--workflow-ref", $env:RELEASE_TOOLING_REF,
|
|
"--workflow-full-ref", $env:RELEASE_TOOLING_FULL_REF,
|
|
"--workflow-sha", $env:RELEASE_TOOLING_SHA
|
|
)
|
|
if (
|
|
$env:RELEASE_TOOLING_REF -ne "main" -and
|
|
$env:RELEASE_TOOLING_FULL_REF -notmatch '^refs/tags/release-publish/'
|
|
) {
|
|
$verifyArgs += "--allow-prevalidated-ref"
|
|
}
|
|
node scripts/release-tooling-identity.mjs @verifyArgs | Out-Null
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Windows release tooling identity is no longer live."
|
|
}
|
|
gh attestation verify $env:APPROVAL_PATH `
|
|
--repo $env:GITHUB_REPOSITORY `
|
|
--signer-workflow "$env:GITHUB_REPOSITORY/.github/workflows/openclaw-release-publish.yml" `
|
|
--source-ref $env:RELEASE_TOOLING_FULL_REF `
|
|
--source-digest $env:RELEASE_TOOLING_SHA `
|
|
--deny-self-hosted-runners
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Windows release approval attestation verification failed."
|
|
}
|
|
gh api `
|
|
"repos/$env:GITHUB_REPOSITORY/actions/runs/$env:RELEASE_PUBLISH_RUN_ID/attempts/$env:EXPECTED_RUN_ATTEMPT" `
|
|
--jq '{workflowName: .name, headBranch: .head_branch, headSha: .head_sha, event, status, conclusion, url: .html_url, runAttempt: .run_attempt, repository: .repository.full_name, path}' |
|
|
node scripts/validate-release-publish-approval.mjs
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Windows release approval does not match the exact parent run."
|
|
}
|
|
}
|
|
|
|
$expectedDigests = $env:EXPECTED_INSTALLER_DIGESTS | ConvertFrom-Json -AsHashtable
|
|
$expectedFiles = @(
|
|
"OpenClawCompanion-Setup-arm64.exe",
|
|
"OpenClawCompanion-Setup-x64.exe",
|
|
"OpenClawCompanion-SHA256SUMS.txt"
|
|
) | Sort-Object
|
|
$actualFiles = @(Get-ChildItem -LiteralPath dist -File | ForEach-Object Name | Sort-Object)
|
|
if (Compare-Object -ReferenceObject $expectedFiles -DifferenceObject $actualFiles) {
|
|
throw "Validated Windows artifact does not match the canonical file contract."
|
|
}
|
|
foreach ($file in Get-ChildItem -LiteralPath dist -File | Sort-Object Name) {
|
|
if ($file.Name -ne "OpenClawCompanion-SHA256SUMS.txt") {
|
|
$expectedHash = ([string]$expectedDigests[$file.Name]) -replace '^sha256:', ''
|
|
$actualHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $file.FullName).Hash
|
|
if ($actualHash -ne $expectedHash) {
|
|
throw "Prepared Windows installer does not match its approved digest: $($file.Name)"
|
|
}
|
|
}
|
|
Assert-ReleaseApproval
|
|
gh release upload $env:RELEASE_TAG $file.FullName `
|
|
--repo $env:GITHUB_REPOSITORY `
|
|
--clobber
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Failed to upload Windows release asset $($file.Name) to $env:RELEASE_TAG."
|
|
}
|
|
}
|
|
|
|
- name: Verify promoted release asset contract
|
|
shell: pwsh
|
|
env:
|
|
RELEASE_TAG: ${{ inputs.tag }}
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
$ErrorActionPreference = "Stop"
|
|
New-Item -ItemType Directory -Force -Path verified | Out-Null
|
|
$expectedAssets = @(Get-ChildItem -LiteralPath dist -File | Sort-Object Name)
|
|
$expectedCompanionAssetNames = @($expectedAssets | ForEach-Object Name | Sort-Object)
|
|
$targetRelease = gh release view $env:RELEASE_TAG `
|
|
--repo $env:GITHUB_REPOSITORY `
|
|
--json assets |
|
|
ConvertFrom-Json
|
|
$actualCompanionAssetNames = @(
|
|
$targetRelease.assets |
|
|
Where-Object { $_.name.StartsWith("OpenClawCompanion-") } |
|
|
ForEach-Object name |
|
|
Sort-Object
|
|
)
|
|
$assetContractDiff = @(
|
|
Compare-Object `
|
|
-ReferenceObject $expectedCompanionAssetNames `
|
|
-DifferenceObject $actualCompanionAssetNames
|
|
)
|
|
if (
|
|
$actualCompanionAssetNames.Count -ne $expectedCompanionAssetNames.Count -or
|
|
$assetContractDiff.Count -ne 0
|
|
) {
|
|
throw "Promoted OpenClawCompanion asset names do not exactly match the current contract."
|
|
}
|
|
|
|
foreach ($asset in $expectedAssets) {
|
|
gh release download $env:RELEASE_TAG `
|
|
--repo $env:GITHUB_REPOSITORY `
|
|
--pattern $asset.Name `
|
|
--dir verified
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Failed to download promoted Windows release asset $($asset.Name)."
|
|
}
|
|
}
|
|
$manifestPath = "verified/OpenClawCompanion-SHA256SUMS.txt"
|
|
$manifestEntries = @(Get-Content -LiteralPath $manifestPath | ForEach-Object {
|
|
if ($_ -notmatch '^([A-Fa-f0-9]{64}) ([^\\/]+)$') {
|
|
throw "Invalid Windows SHA-256 manifest entry: $_"
|
|
}
|
|
[PSCustomObject]@{
|
|
Hash = $Matches[1]
|
|
Name = $Matches[2]
|
|
}
|
|
})
|
|
$expectedInstallerNames = @(
|
|
$expectedAssets |
|
|
Where-Object Name -ne "OpenClawCompanion-SHA256SUMS.txt" |
|
|
ForEach-Object Name
|
|
)
|
|
$manifestInstallerNames = @($manifestEntries | ForEach-Object Name | Sort-Object)
|
|
if (Compare-Object -ReferenceObject $expectedInstallerNames -DifferenceObject $manifestInstallerNames) {
|
|
throw "Promoted Windows SHA-256 manifest does not match the installer asset contract."
|
|
}
|
|
foreach ($entry in $manifestEntries) {
|
|
$hash = (Get-FileHash -Algorithm SHA256 -LiteralPath "verified/$($entry.Name)").Hash
|
|
if ($hash -ne $entry.Hash) {
|
|
throw "Promoted Windows release asset checksum mismatch: $($entry.Name)"
|
|
}
|
|
}
|
|
|
|
- name: Summary
|
|
shell: pwsh
|
|
env:
|
|
RELEASE_TAG: ${{ inputs.tag }}
|
|
WINDOWS_NODE_TAG: ${{ inputs.windows_node_tag }}
|
|
run: |
|
|
@"
|
|
## Windows Hub installers promoted
|
|
|
|
OpenClaw release: $env:RELEASE_TAG
|
|
Source release: openclaw/openclaw-windows-node@$env:WINDOWS_NODE_TAG
|
|
"@ >> $env:GITHUB_STEP_SUMMARY
|
|
Get-ChildItem -LiteralPath dist -File |
|
|
Sort-Object Name |
|
|
ForEach-Object {
|
|
"- https://github.com/openclaw/openclaw/releases/download/$env:RELEASE_TAG/$($_.Name)"
|
|
} >> $env:GITHUB_STEP_SUMMARY
|