-
Notifications
You must be signed in to change notification settings - Fork 112
Adds release helper scripts #37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| name: Release | ||
|
|
||
| on: | ||
| push: | ||
| tags: | ||
| - "v*.*.*" | ||
|
|
||
| permissions: | ||
| contents: write | ||
|
|
||
| env: | ||
| SOLUTION_FILE_PATH: Injector.sln | ||
| BUILD_CONFIGURATION: Release | ||
|
|
||
| jobs: | ||
| build: | ||
| runs-on: windows-latest | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| platform: [Win32, x64, ARM64] | ||
|
|
||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Add MSBuild to PATH | ||
| uses: microsoft/setup-msbuild@v2 | ||
|
|
||
| - name: Build | ||
| run: msbuild /m /p:Configuration="${{ env.BUILD_CONFIGURATION }}" /p:Platform=${{ matrix.platform }} ${{ env.SOLUTION_FILE_PATH }} | ||
|
|
||
| - name: Stage binary artifact | ||
| shell: pwsh | ||
| run: | | ||
| $source = Join-Path "${{ github.workspace }}" "bin/${{ matrix.platform }}/Injector.exe" | ||
| if (!(Test-Path $source)) { | ||
| throw "Expected binary not found: $source" | ||
| } | ||
|
|
||
| $targetDir = Join-Path "${{ github.workspace }}" "artifacts/${{ matrix.platform }}" | ||
| New-Item -ItemType Directory -Force -Path $targetDir | Out-Null | ||
| Copy-Item -Path $source -Destination (Join-Path $targetDir "Injector.exe") -Force | ||
|
|
||
| - name: Upload platform artifact | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: Injector-${{ matrix.platform }} | ||
| path: artifacts/${{ matrix.platform }}/Injector.exe | ||
| if-no-files-found: error | ||
| retention-days: 1 | ||
|
|
||
| package-and-release: | ||
| runs-on: windows-latest | ||
| needs: build | ||
|
|
||
| steps: | ||
| - name: Download Win32 artifact | ||
| uses: actions/download-artifact@v4 | ||
| with: | ||
| name: Injector-Win32 | ||
| path: downloaded/Win32 | ||
|
|
||
| - name: Download x64 artifact | ||
| uses: actions/download-artifact@v4 | ||
| with: | ||
| name: Injector-x64 | ||
| path: downloaded/x64 | ||
|
|
||
| - name: Download ARM64 artifact | ||
| uses: actions/download-artifact@v4 | ||
| with: | ||
| name: Injector-ARM64 | ||
| path: downloaded/ARM64 | ||
|
|
||
| - name: Assemble zip structure | ||
| shell: pwsh | ||
| run: | | ||
| $releaseRoot = Join-Path "${{ github.workspace }}" "release-content" | ||
| $zipPath = Join-Path "${{ github.workspace }}" "Injector_x86_amd64_arm64_unsigned.zip" | ||
|
|
||
| if (Test-Path $releaseRoot) { | ||
| Remove-Item -Path $releaseRoot -Recurse -Force | ||
| } | ||
| if (Test-Path $zipPath) { | ||
| Remove-Item -Path $zipPath -Force | ||
| } | ||
|
|
||
| $arm64Dir = Join-Path $releaseRoot "ARM64" | ||
| $win32Dir = Join-Path $releaseRoot "Win32" | ||
| $x64Dir = Join-Path $releaseRoot "x64" | ||
| New-Item -ItemType Directory -Force -Path $arm64Dir, $win32Dir, $x64Dir | Out-Null | ||
|
|
||
| Copy-Item -Path "downloaded/ARM64/Injector.exe" -Destination (Join-Path $arm64Dir "Injector.exe") -Force | ||
| Copy-Item -Path "downloaded/Win32/Injector.exe" -Destination (Join-Path $win32Dir "Injector.exe") -Force | ||
| Copy-Item -Path "downloaded/x64/Injector.exe" -Destination (Join-Path $x64Dir "Injector.exe") -Force | ||
|
|
||
| Compress-Archive -Path (Join-Path $releaseRoot "*") -DestinationPath $zipPath | ||
|
|
||
| - name: Upload unsigned release bundle artifact | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: unsigned-release-bundle-${{ github.ref_name }} | ||
| path: Injector_x86_amd64_arm64_unsigned.zip | ||
| if-no-files-found: error | ||
| retention-days: 14 | ||
|
|
||
| - name: Create draft release awaiting EV signing | ||
| uses: softprops/action-gh-release@v2 | ||
| with: | ||
| draft: true | ||
| body: | | ||
| Release draft created automatically. | ||
| Final asset must be EV-signed on the maintainer local machine. | ||
|
|
||
| Expected final asset name: | ||
| - Injector_x86_amd64_arm64.zip | ||
| generate_release_notes: true | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| # Release process | ||
|
|
||
| ## Overview | ||
|
|
||
| Pushing a tag like `v1.5.0` triggers `.github/workflows/release.yml`, which: | ||
|
|
||
| - Builds `Injector.exe` for `Win32`, `x64`, and `ARM64` | ||
| - Creates an unsigned bundle artifact: `Injector_x86_amd64_arm64_unsigned.zip` | ||
| - Creates a draft GitHub release for the tag | ||
|
|
||
| Final signing and publish are performed locally on the EV-capable machine. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| 1. `gh` CLI installed and authenticated (`gh auth status`) | ||
| 2. [`wdkwhere`](https://github.com/nefarius/wdkwhere) installed and available in `PATH` | ||
| 3. EV token/certificate available and unlocked | ||
|
|
||
| ## Finalize a tagged release | ||
|
|
||
| Run from repository root: | ||
|
|
||
| ```powershell | ||
| .\scripts\finalize-release.ps1 -Tag v1.5.0 -CertificateSubjectName "Nefarius Software Solutions e.U." | ||
| ``` | ||
|
|
||
| The script will: | ||
|
|
||
| - Download `unsigned-release-bundle-v1.5.0` automatically (unless `-UnsignedZipPath` is provided) | ||
| - Sign: | ||
| - `ARM64/Injector.exe` | ||
| - `Win32/Injector.exe` | ||
| - `x64/Injector.exe` | ||
| - Create `Injector_x86_amd64_arm64.zip` | ||
| - Upload it to the draft release and publish it | ||
|
|
||
| ## Useful options | ||
|
|
||
| ```powershell | ||
| # Upload signed zip but keep release as draft | ||
| .\scripts\finalize-release.ps1 -Tag v1.5.0 -CertificateSubjectName "Nefarius Software Solutions e.U." -NoPublish | ||
|
|
||
| # Use a manually downloaded unsigned zip | ||
| .\scripts\finalize-release.ps1 -Tag v1.5.0 -CertificateSubjectName "Nefarius Software Solutions e.U." -UnsignedZipPath "C:\Temp\Injector_x86_amd64_arm64_unsigned.zip" | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| param( | ||
| [Parameter(Mandatory = $true)] | ||
| [ValidatePattern("^v\d+\.\d+\.\d+$")] | ||
| [string]$Tag, | ||
|
|
||
| [Parameter(Mandatory = $true)] | ||
| [string]$CertificateSubjectName, | ||
|
|
||
| [string]$TimestampUrl = "http://timestamp.digicert.com", | ||
| [string]$UnsignedZipPath, | ||
| [string]$WorkspaceRoot = (Join-Path $PSScriptRoot ".."), | ||
| [string]$OutputDir = (Join-Path $PSScriptRoot "../.release-local"), | ||
| [switch]$NoPublish | ||
| ) | ||
|
|
||
| Set-StrictMode -Version Latest | ||
| $ErrorActionPreference = "Stop" | ||
|
|
||
| function Ensure-WdkWhere { | ||
| $command = Get-Command wdkwhere -ErrorAction SilentlyContinue | ||
| if (!$command) { | ||
| throw "wdkwhere was not found in PATH. Install it first (dotnet tool install --global Nefarius.Tools.WDKWhere)." | ||
| } | ||
|
|
||
| return $command.Source | ||
| } | ||
|
|
||
| function Resolve-UnsignedZip { | ||
| param( | ||
| [string]$TagValue, | ||
| [string]$ExplicitZipPath, | ||
| [string]$DestinationDir | ||
| ) | ||
|
|
||
| if ($ExplicitZipPath) { | ||
| if (!(Test-Path $ExplicitZipPath)) { | ||
| throw "Unsigned zip path not found: $ExplicitZipPath" | ||
| } | ||
|
|
||
| return (Resolve-Path $ExplicitZipPath).Path | ||
| } | ||
|
|
||
| $workflowName = "release.yml" | ||
| $artifactName = "unsigned-release-bundle-$TagValue" | ||
| $runRows = gh run list --workflow $workflowName --limit 100 --json databaseId,headBranch,displayTitle,status,conclusion,event | ConvertFrom-Json | ||
| if (!$runRows) { | ||
| throw "No workflow runs found for '$workflowName'." | ||
| } | ||
|
|
||
| $run = $runRows | | ||
| Where-Object { | ||
| $_.event -eq "push" -and | ||
| $_.status -eq "completed" -and | ||
| $_.conclusion -eq "success" -and | ||
| ($_.headBranch -eq $TagValue -or $_.displayTitle -eq $TagValue) | ||
| } | | ||
| Select-Object -First 1 | ||
|
|
||
| if (!$run) { | ||
| throw "No successful '$workflowName' run found for tag '$TagValue'." | ||
| } | ||
|
|
||
| $downloadDir = Join-Path $DestinationDir "downloaded" | ||
| New-Item -ItemType Directory -Path $downloadDir -Force | Out-Null | ||
|
|
||
| gh run download $run.databaseId -n $artifactName -D $downloadDir | Out-Null | ||
|
|
||
| $zip = Get-ChildItem -Path $downloadDir -Filter "Injector_x86_amd64_arm64_unsigned.zip" -File -Recurse | Select-Object -First 1 | ||
| if (!$zip) { | ||
| throw "Downloaded artifact '$artifactName' did not contain Injector_x86_amd64_arm64_unsigned.zip." | ||
| } | ||
|
|
||
| return $zip.FullName | ||
| } | ||
|
|
||
| function Sign-Binary { | ||
| param( | ||
| [string]$WdkWherePath, | ||
| [string]$CertSubjectName, | ||
| [string]$Timestamp, | ||
| [string]$FilePath | ||
| ) | ||
|
|
||
| if (!(Test-Path $FilePath)) { | ||
| throw "Expected binary missing: $FilePath" | ||
| } | ||
|
|
||
| & $WdkWherePath run signtool sign /n $CertSubjectName /a /fd SHA256 /td SHA256 /tr $Timestamp $FilePath | ||
| if ($LASTEXITCODE -ne 0) { | ||
| throw "signtool failed for '$FilePath' with exit code $LASTEXITCODE." | ||
| } | ||
| } | ||
|
|
||
| Push-Location $WorkspaceRoot | ||
| try { | ||
| # Validate GH auth early because this script relies on release + artifact APIs. | ||
| gh auth status | Out-Null | ||
|
|
||
| $wdkWhere = Ensure-WdkWhere | ||
| Write-Host "Using wdkwhere: $wdkWhere" | ||
|
|
||
| $resolvedOutputDir = Resolve-Path (New-Item -ItemType Directory -Path $OutputDir -Force) | ||
| $workRoot = Join-Path $resolvedOutputDir ".work-$Tag" | ||
| if (Test-Path $workRoot) { | ||
| Remove-Item -Path $workRoot -Recurse -Force | ||
| } | ||
| New-Item -ItemType Directory -Path $workRoot -Force | Out-Null | ||
|
|
||
| $unsignedZip = Resolve-UnsignedZip -TagValue $Tag -ExplicitZipPath $UnsignedZipPath -DestinationDir $workRoot | ||
| Write-Host "Using unsigned zip: $unsignedZip" | ||
|
|
||
| $unsignedExtract = Join-Path $workRoot "unsigned" | ||
| Expand-Archive -Path $unsignedZip -DestinationPath $unsignedExtract -Force | ||
|
|
||
| $targets = @( | ||
| (Join-Path $unsignedExtract "ARM64/Injector.exe"), | ||
| (Join-Path $unsignedExtract "Win32/Injector.exe"), | ||
| (Join-Path $unsignedExtract "x64/Injector.exe") | ||
| ) | ||
|
|
||
| foreach ($file in $targets) { | ||
| Write-Host "Signing $file" | ||
| Sign-Binary -WdkWherePath $wdkWhere -CertSubjectName $CertificateSubjectName -Timestamp $TimestampUrl -FilePath $file | ||
| } | ||
|
|
||
| $finalZip = Join-Path $resolvedOutputDir "Injector_x86_amd64_arm64.zip" | ||
| if (Test-Path $finalZip) { | ||
| Remove-Item -Path $finalZip -Force | ||
| } | ||
| Compress-Archive -Path (Join-Path $unsignedExtract "*") -DestinationPath $finalZip | ||
| Write-Host "Created signed zip: $finalZip" | ||
|
|
||
| gh release view $Tag --json tagName,isDraft | Out-Null | ||
| gh release upload $Tag $finalZip --clobber | Out-Null | ||
| Write-Host "Uploaded asset to release '$Tag'." | ||
|
|
||
| if (-not $NoPublish) { | ||
| gh release edit $Tag --draft=false | Out-Null | ||
| Write-Host "Published release '$Tag'." | ||
| } | ||
| else { | ||
| Write-Host "Draft release left unpublished due to -NoPublish." | ||
| } | ||
| } | ||
| finally { | ||
| Pop-Location | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.