diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7b44800fe..bb80ec1cb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -83,8 +83,27 @@ jobs: if: matrix.platform == 'win' run: pnpm run package:win - - name: Validate unsigned Windows artifacts before SignPath + # Detect release channel from tag to skip code signing for alpha/beta builds + - name: Detect Windows release channel if: matrix.platform == 'win' + id: win-channel + shell: bash + run: | + if [[ "${{ github.ref }}" == refs/tags/v* ]]; then + TAG="${GITHUB_REF#refs/tags/v}" + else + TAG="${{ github.event.inputs.version }}" + fi + if [[ "$TAG" =~ (alpha|beta) ]]; then + echo "is_stable=false" >> $GITHUB_OUTPUT + echo "Channel: prerelease ($TAG) — skipping code signing" + else + echo "is_stable=true" >> $GITHUB_OUTPUT + echo "Channel: stable ($TAG) — will sign" + fi + + - name: Validate unsigned Windows artifacts before SignPath + if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true' shell: pwsh run: | $unsignedExeFiles = Get-ChildItem -Path "release" -Filter *.exe -File @@ -97,7 +116,7 @@ jobs: $unsignedExeFiles | ForEach-Object { Write-Host " - $($_.Name)" } - name: Upload unsigned Windows artifacts for SignPath - if: matrix.platform == 'win' + if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true' id: upload-unsigned-windows-artifact uses: actions/upload-artifact@v4 with: @@ -106,7 +125,7 @@ jobs: retention-days: 1 - name: Sign Windows artifacts via SignPath - if: matrix.platform == 'win' + if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true' id: signpath-sign-windows uses: signpath/github-action-submit-signing-request@v2 with: @@ -119,7 +138,7 @@ jobs: output-artifact-directory: release/signed - name: Replace unsigned executables with signed ones - if: matrix.platform == 'win' + if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true' shell: pwsh run: | Write-Host "SignPath GitHub artifact ID: ${{ steps.upload-unsigned-windows-artifact.outputs.artifact-id }}" @@ -140,6 +159,72 @@ jobs: } Write-Host "Signed executables copied to release/ ($($finalExeFiles.Count) file(s))" + # Code signing changes the .exe binary, invalidating the sha512 hash that + # electron-builder wrote into latest.yml during the initial build. + # Recalculate the hash for each signed .exe and patch the yml files so + # electron-updater can verify the download successfully. + # + # Actual latest.yml structure (from electron-builder NSIS): + # files: + # - url: ClawX-0.2.4-win-x64.exe ← files[] entries have url/sha512/size + # sha512: + # size: 430775882 + # path: ClawX-0.2.4-win-arm64.exe ← top-level has path/sha512 (no size!) + # sha512: + # releaseDate: '...' + - name: Update latest.yml sha512 after code signing + if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true' + shell: pwsh + run: | + $ymlFiles = Get-ChildItem -Path "release" -Filter "*.yml" -File | Where-Object { $_.Name -ne "builder-debug.yml" } + $exeFiles = Get-ChildItem -Path "release" -Filter "*.exe" -File + + foreach ($yml in $ymlFiles) { + $content = Get-Content $yml.FullName -Raw + $modified = $false + + foreach ($exe in $exeFiles) { + # Compute new sha512 (base64) for the signed exe + $hash = Get-FileHash -Path $exe.FullName -Algorithm SHA512 + $hashBytes = [byte[]]::new($hash.Hash.Length / 2) + for ($i = 0; $i -lt $hashBytes.Length; $i++) { + $hashBytes[$i] = [Convert]::ToByte($hash.Hash.Substring($i * 2, 2), 16) + } + $newSha512 = [Convert]::ToBase64String($hashBytes) + $newSize = (Get-Item $exe.FullName).Length + $escapedName = [Regex]::Escape($exe.Name) + + # 1) files[] entries: url: \n sha512: \n size: + $urlPattern = "(?m)(url:\s*${escapedName}\s*\r?\n\s*sha512:\s*)(\S+)(\s*\r?\n\s*size:\s*)(\d+)" + if ($content -match $urlPattern) { + $content = $content -replace $urlPattern, "`${1}${newSha512}`${3}${newSize}" + $modified = $true + Write-Host "Updated $($yml.Name) files[]: $($exe.Name) sha512=$newSha512 size=$newSize" + } + + # 2) Top-level entry: path: \nsha512: \n (no size field) + $pathPattern = "(?m)(path:\s*${escapedName}\s*\r?\n)sha512:\s*\S+" + if ($content -match $pathPattern) { + $content = $content -replace $pathPattern, "`${1}sha512: ${newSha512}" + $modified = $true + Write-Host "Updated $($yml.Name) top-level: $($exe.Name) sha512=$newSha512" + } + } + + if ($modified) { + Set-Content -Path $yml.FullName -Value $content -NoNewline + Write-Host "Saved updated $($yml.Name)" + } + } + + Write-Host "" + Write-Host "=== Final yml contents ===" + foreach ($yml in $ymlFiles) { + Write-Host "--- $($yml.Name) ---" + Get-Content $yml.FullName + Write-Host "" + } + # Linux specific steps - name: Build Linux if: matrix.platform == 'linux' diff --git a/package.json b/package.json index d91e9920e..1a4c8d1b3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "clawx", - "version": "0.2.4", + "version": "0.2.5-beta.1", "pnpm": { "onlyBuiltDependencies": [ "@discordjs/opus", @@ -125,4 +125,4 @@ "zx": "^8.8.5" }, "packageManager": "pnpm@10.31.0+sha512.e3927388bfaa8078ceb79b748ffc1e8274e84d75163e67bc22e06c0d3aed43dd153151cbf11d7f8301ff4acb98c68bdc5cadf6989532801ffafe3b3e4a63c268" -} +} \ No newline at end of file