This commit is contained in:
paisley
2026-03-20 10:37:14 +08:00
Unverified
parent 0ff5c90bb8
commit a30f2238a4
2 changed files with 131 additions and 131 deletions

View File

@@ -83,147 +83,147 @@ jobs:
if: matrix.platform == 'win' if: matrix.platform == 'win'
run: pnpm run package:win run: pnpm run package:win
# Detect release channel from tag to skip code signing for alpha/beta builds # # Detect release channel from tag to skip code signing for alpha/beta builds
- name: Detect Windows release channel # - name: Detect Windows release channel
if: matrix.platform == 'win' # if: matrix.platform == 'win'
id: win-channel # id: win-channel
shell: bash # shell: bash
run: | # run: |
if [[ "${{ github.ref }}" == refs/tags/v* ]]; then # if [[ "${{ github.ref }}" == refs/tags/v* ]]; then
TAG="${GITHUB_REF#refs/tags/v}" # TAG="${GITHUB_REF#refs/tags/v}"
else # else
TAG="${{ github.event.inputs.version }}" # TAG="${{ github.event.inputs.version }}"
fi # fi
if [[ "$TAG" =~ (alpha|beta) ]]; then # if [[ "$TAG" =~ (alpha|beta) ]]; then
echo "is_stable=false" >> $GITHUB_OUTPUT # echo "is_stable=false" >> $GITHUB_OUTPUT
echo "Channel: prerelease ($TAG) — skipping code signing" # echo "Channel: prerelease ($TAG) — skipping code signing"
else # else
echo "is_stable=true" >> $GITHUB_OUTPUT # echo "is_stable=true" >> $GITHUB_OUTPUT
echo "Channel: stable ($TAG) — will sign" # echo "Channel: stable ($TAG) — will sign"
fi # fi
- name: Validate unsigned Windows artifacts before SignPath # - name: Validate unsigned Windows artifacts before SignPath
if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true' # if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true'
shell: pwsh # shell: pwsh
run: | # run: |
$unsignedExeFiles = Get-ChildItem -Path "release" -Filter *.exe -File # $unsignedExeFiles = Get-ChildItem -Path "release" -Filter *.exe -File
if (-not $unsignedExeFiles) { # if (-not $unsignedExeFiles) {
throw "No unsigned .exe files found in release/ before SignPath upload" # throw "No unsigned .exe files found in release/ before SignPath upload"
} # }
$unsignedCount = $unsignedExeFiles.Count # $unsignedCount = $unsignedExeFiles.Count
"UNSIGNED_EXE_COUNT=$unsignedCount" | Out-File -FilePath $env:GITHUB_ENV -Append # "UNSIGNED_EXE_COUNT=$unsignedCount" | Out-File -FilePath $env:GITHUB_ENV -Append
Write-Host "Found $unsignedCount unsigned .exe file(s):" # Write-Host "Found $unsignedCount unsigned .exe file(s):"
$unsignedExeFiles | ForEach-Object { Write-Host " - $($_.Name)" } # $unsignedExeFiles | ForEach-Object { Write-Host " - $($_.Name)" }
- name: Upload unsigned Windows artifacts for SignPath # - name: Upload unsigned Windows artifacts for SignPath
if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true' # if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true'
id: upload-unsigned-windows-artifact # id: upload-unsigned-windows-artifact
uses: actions/upload-artifact@v4 # uses: actions/upload-artifact@v4
with: # with:
name: unsigned-win-exe-${{ github.run_id }}-${{ github.run_attempt }} # name: unsigned-win-exe-${{ github.run_id }}-${{ github.run_attempt }}
path: release/*.exe # path: release/*.exe
retention-days: 1 # retention-days: 1
- name: Sign Windows artifacts via SignPath # - name: Sign Windows artifacts via SignPath
if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true' # if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true'
id: signpath-sign-windows # id: signpath-sign-windows
uses: signpath/github-action-submit-signing-request@v2 # uses: signpath/github-action-submit-signing-request@v2
with: # with:
api-token: ${{ secrets.SIGNPATH_API_TOKEN }} # api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
organization-id: "78e37079-23df-4800-b41c-33312ad7c1e3" # organization-id: "78e37079-23df-4800-b41c-33312ad7c1e3"
project-slug: "ValueCell" # project-slug: "ValueCell"
signing-policy-slug: "ValueCell-sign" # signing-policy-slug: "ValueCell-sign"
github-artifact-id: ${{ steps.upload-unsigned-windows-artifact.outputs.artifact-id }} # github-artifact-id: ${{ steps.upload-unsigned-windows-artifact.outputs.artifact-id }}
wait-for-completion: true # wait-for-completion: true
output-artifact-directory: release/signed # output-artifact-directory: release/signed
- name: Replace unsigned executables with signed ones # - name: Replace unsigned executables with signed ones
if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true' # if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true'
shell: pwsh # shell: pwsh
run: | # run: |
Write-Host "SignPath GitHub artifact ID: ${{ steps.upload-unsigned-windows-artifact.outputs.artifact-id }}" # Write-Host "SignPath GitHub artifact ID: ${{ steps.upload-unsigned-windows-artifact.outputs.artifact-id }}"
$signedExeFiles = Get-ChildItem -Path "release/signed" -Filter *.exe -File -Recurse # $signedExeFiles = Get-ChildItem -Path "release/signed" -Filter *.exe -File -Recurse
if (-not $signedExeFiles) { # if (-not $signedExeFiles) {
throw "No signed .exe files found in release/signed" # throw "No signed .exe files found in release/signed"
} # }
$signedCount = $signedExeFiles.Count # $signedCount = $signedExeFiles.Count
if ($env:UNSIGNED_EXE_COUNT -and ($signedCount -ne [int]$env:UNSIGNED_EXE_COUNT)) { # if ($env:UNSIGNED_EXE_COUNT -and ($signedCount -ne [int]$env:UNSIGNED_EXE_COUNT)) {
throw "Signed .exe count ($signedCount) does not match unsigned count ($env:UNSIGNED_EXE_COUNT)" # throw "Signed .exe count ($signedCount) does not match unsigned count ($env:UNSIGNED_EXE_COUNT)"
} # }
foreach ($file in $signedExeFiles) { # foreach ($file in $signedExeFiles) {
Copy-Item -Path $file.FullName -Destination "release/$($file.Name)" -Force # Copy-Item -Path $file.FullName -Destination "release/$($file.Name)" -Force
} # }
$finalExeFiles = Get-ChildItem -Path "release" -Filter *.exe -File # $finalExeFiles = Get-ChildItem -Path "release" -Filter *.exe -File
if ($env:UNSIGNED_EXE_COUNT -and ($finalExeFiles.Count -ne [int]$env:UNSIGNED_EXE_COUNT)) { # if ($env:UNSIGNED_EXE_COUNT -and ($finalExeFiles.Count -ne [int]$env:UNSIGNED_EXE_COUNT)) {
throw "Final release .exe count ($($finalExeFiles.Count)) does not match unsigned count ($env:UNSIGNED_EXE_COUNT)" # throw "Final release .exe count ($($finalExeFiles.Count)) does not match unsigned count ($env:UNSIGNED_EXE_COUNT)"
} # }
Write-Host "Signed executables copied to release/ ($($finalExeFiles.Count) file(s))" # Write-Host "Signed executables copied to release/ ($($finalExeFiles.Count) file(s))"
# Code signing changes the .exe binary, invalidating the sha512 hash that # # Code signing changes the .exe binary, invalidating the sha512 hash that
# electron-builder wrote into latest.yml during the initial build. # # electron-builder wrote into latest.yml during the initial build.
# Recalculate the hash for each signed .exe and patch the yml files so # # Recalculate the hash for each signed .exe and patch the yml files so
# electron-updater can verify the download successfully. # # electron-updater can verify the download successfully.
# # #
# Actual latest.yml structure (from electron-builder NSIS): # # Actual latest.yml structure (from electron-builder NSIS):
# files: # # files:
# - url: ClawX-0.2.4-win-x64.exe ← files[] entries have url/sha512/size # # - url: ClawX-0.2.4-win-x64.exe ← files[] entries have url/sha512/size
# sha512: <base64> # # sha512: <base64>
# size: 430775882 # # size: 430775882
# path: ClawX-0.2.4-win-arm64.exe ← top-level has path/sha512 (no size!) # # path: ClawX-0.2.4-win-arm64.exe ← top-level has path/sha512 (no size!)
# sha512: <base64> # # sha512: <base64>
# releaseDate: '...' # # releaseDate: '...'
- name: Update latest.yml sha512 after code signing # - name: Update latest.yml sha512 after code signing
if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true' # if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true'
shell: pwsh # shell: pwsh
run: | # run: |
$ymlFiles = Get-ChildItem -Path "release" -Filter "*.yml" -File | Where-Object { $_.Name -ne "builder-debug.yml" } # $ymlFiles = Get-ChildItem -Path "release" -Filter "*.yml" -File | Where-Object { $_.Name -ne "builder-debug.yml" }
$exeFiles = Get-ChildItem -Path "release" -Filter "*.exe" -File # $exeFiles = Get-ChildItem -Path "release" -Filter "*.exe" -File
foreach ($yml in $ymlFiles) { # foreach ($yml in $ymlFiles) {
$content = Get-Content $yml.FullName -Raw # $content = Get-Content $yml.FullName -Raw
$modified = $false # $modified = $false
foreach ($exe in $exeFiles) { # foreach ($exe in $exeFiles) {
# Compute new sha512 (base64) for the signed exe # # Compute new sha512 (base64) for the signed exe
$hash = Get-FileHash -Path $exe.FullName -Algorithm SHA512 # $hash = Get-FileHash -Path $exe.FullName -Algorithm SHA512
$hashBytes = [byte[]]::new($hash.Hash.Length / 2) # $hashBytes = [byte[]]::new($hash.Hash.Length / 2)
for ($i = 0; $i -lt $hashBytes.Length; $i++) { # for ($i = 0; $i -lt $hashBytes.Length; $i++) {
$hashBytes[$i] = [Convert]::ToByte($hash.Hash.Substring($i * 2, 2), 16) # $hashBytes[$i] = [Convert]::ToByte($hash.Hash.Substring($i * 2, 2), 16)
} # }
$newSha512 = [Convert]::ToBase64String($hashBytes) # $newSha512 = [Convert]::ToBase64String($hashBytes)
$newSize = (Get-Item $exe.FullName).Length # $newSize = (Get-Item $exe.FullName).Length
$escapedName = [Regex]::Escape($exe.Name) # $escapedName = [Regex]::Escape($exe.Name)
# 1) files[] entries: url: <name>\n sha512: <hash>\n size: <n> # # 1) files[] entries: url: <name>\n sha512: <hash>\n size: <n>
$urlPattern = "(?m)(url:\s*${escapedName}\s*\r?\n\s*sha512:\s*)(\S+)(\s*\r?\n\s*size:\s*)(\d+)" # $urlPattern = "(?m)(url:\s*${escapedName}\s*\r?\n\s*sha512:\s*)(\S+)(\s*\r?\n\s*size:\s*)(\d+)"
if ($content -match $urlPattern) { # if ($content -match $urlPattern) {
$content = $content -replace $urlPattern, "`${1}${newSha512}`${3}${newSize}" # $content = $content -replace $urlPattern, "`${1}${newSha512}`${3}${newSize}"
$modified = $true # $modified = $true
Write-Host "Updated $($yml.Name) files[]: $($exe.Name) sha512=$newSha512 size=$newSize" # Write-Host "Updated $($yml.Name) files[]: $($exe.Name) sha512=$newSha512 size=$newSize"
} # }
# 2) Top-level entry: path: <name>\nsha512: <hash>\n (no size field) # # 2) Top-level entry: path: <name>\nsha512: <hash>\n (no size field)
$pathPattern = "(?m)(path:\s*${escapedName}\s*\r?\n)sha512:\s*\S+" # $pathPattern = "(?m)(path:\s*${escapedName}\s*\r?\n)sha512:\s*\S+"
if ($content -match $pathPattern) { # if ($content -match $pathPattern) {
$content = $content -replace $pathPattern, "`${1}sha512: ${newSha512}" # $content = $content -replace $pathPattern, "`${1}sha512: ${newSha512}"
$modified = $true # $modified = $true
Write-Host "Updated $($yml.Name) top-level: $($exe.Name) sha512=$newSha512" # Write-Host "Updated $($yml.Name) top-level: $($exe.Name) sha512=$newSha512"
} # }
} # }
if ($modified) { # if ($modified) {
Set-Content -Path $yml.FullName -Value $content -NoNewline # Set-Content -Path $yml.FullName -Value $content -NoNewline
Write-Host "Saved updated $($yml.Name)" # Write-Host "Saved updated $($yml.Name)"
} # }
} # }
Write-Host "" # Write-Host ""
Write-Host "=== Final yml contents ===" # Write-Host "=== Final yml contents ==="
foreach ($yml in $ymlFiles) { # foreach ($yml in $ymlFiles) {
Write-Host "--- $($yml.Name) ---" # Write-Host "--- $($yml.Name) ---"
Get-Content $yml.FullName # Get-Content $yml.FullName
Write-Host "" # Write-Host ""
} # }
# Linux specific steps # Linux specific steps
- name: Build Linux - name: Build Linux

View File

@@ -1,6 +1,6 @@
{ {
"name": "clawx", "name": "clawx",
"version": "0.2.7-beta.0", "version": "0.2.7",
"pnpm": { "pnpm": {
"onlyBuiltDependencies": [ "onlyBuiltDependencies": [
"@discordjs/opus", "@discordjs/opus",