From bec1c87d0beaf50b9363ab1b1d173031be9cd901 Mon Sep 17 00:00:00 2001 From: Bjarn Bronsveld Date: Wed, 16 Sep 2026 00:58:48 +0200 Subject: [PATCH 1/2] fix(release): initialize the Windows signing repository --- .github/workflows/release.yml | 7 +++-- .github/workflows/test.yml | 10 +++++++ scripts/install-signing-client.ps1 | 29 ++++++++++++++++++ scripts/test-signing-client.ps1 | 47 ++++++++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 scripts/install-signing-client.ps1 create mode 100644 scripts/test-signing-client.ps1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b55c491..77c79d4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -79,6 +79,10 @@ jobs: MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }} MACOS_SIGN_TEAM_ID: ${{ vars.MACOS_SIGN_TEAM_ID }} run: python scripts/release.py settings + - name: Install the pinned Artifact Signing client + if: steps.saved.outputs.restored != 'true' + shell: pwsh + run: ./scripts/install-signing-client.ps1 - name: Sign in to Azure with OIDC if: steps.saved.outputs.restored != 'true' uses: azure/login@8216e11d8cd9b42fe925c852af8e76311ff067ac # v2 @@ -86,7 +90,7 @@ jobs: client-id: ${{ vars.AZURE_CLIENT_ID }} tenant-id: ${{ vars.AZURE_TENANT_ID }} subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }} - - name: Install the Artifact Signing client and sign the installers + - name: Sign the Windows installers if: steps.saved.outputs.restored != 'true' shell: pwsh env: @@ -94,7 +98,6 @@ jobs: ARTIFACT_SIGNING_ACCOUNT_NAME: ${{ vars.ARTIFACT_SIGNING_ACCOUNT_NAME }} ARTIFACT_SIGNING_CERTIFICATE_PROFILE_NAME: ${{ vars.ARTIFACT_SIGNING_CERTIFICATE_PROFILE_NAME }} run: | - Install-Module -Name ArtifactSigning -RequiredVersion 0.1.8 -Scope CurrentUser -Force -Repository PSGallery python scripts/release.py prepare-windows if ($LASTEXITCODE -ne 0) { throw 'Installer preparation failed.' } Get-ChildItem packaging/*.ps1 | ForEach-Object { ./scripts/sign-windows.ps1 -Path $_.FullName } diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8a4043c..9abea9f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,6 +17,16 @@ on: permissions: contents: read jobs: + signing-client: + runs-on: windows-2025 + timeout-minutes: 15 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + persist-credentials: false + - name: Recover the signing client from a missing package repository + shell: pwsh + run: ./scripts/test-signing-client.ps1 saved-release: if: github.event_name == 'workflow_dispatch' && inputs.release_run_id != '' permissions: diff --git a/scripts/install-signing-client.ps1 b/scripts/install-signing-client.ps1 new file mode 100644 index 0000000..853ed66 --- /dev/null +++ b/scripts/install-signing-client.ps1 @@ -0,0 +1,29 @@ +#Requires -Version 7.2 +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +for ($attempt = 1; $attempt -le 3; $attempt++) { + try { + $gallery = @(Get-PSRepository -ErrorAction Stop | Where-Object Name -eq 'PSGallery') + if ($gallery.Count -eq 0) { + Register-PSRepository -Default -ErrorAction Stop + $gallery = @(Get-PSRepository -Name PSGallery -ErrorAction Stop) + } + if ($gallery.Count -ne 1 -or $gallery[0].SourceLocation.TrimEnd('/') -cne 'https://www.powershellgallery.com/api/v2') { + throw [System.Security.SecurityException]::new('PSGallery must use the official HTTPS package source.') + } + Install-Module -Name ArtifactSigning -RequiredVersion 0.1.8 -Scope CurrentUser -Force -Repository PSGallery -ErrorAction Stop + $module = Import-Module ArtifactSigning -RequiredVersion 0.1.8 -Force -PassThru -ErrorAction Stop + if ($module.Version -ne [version]'0.1.8' -or -not $module.ExportedCommands.ContainsKey('Invoke-ArtifactSigning')) { + throw 'The pinned Artifact Signing client did not load.' + } + Write-Output 'ArtifactSigning 0.1.8 is installed and ready.' + return + } catch [System.Security.SecurityException] { + throw + } catch { + if ($attempt -eq 3) { throw } + Write-Warning "Artifact Signing setup attempt $attempt failed: $($_.Exception.Message). Retrying." + Start-Sleep -Seconds (5 * $attempt) + } +} diff --git a/scripts/test-signing-client.ps1 b/scripts/test-signing-client.ps1 new file mode 100644 index 0000000..4485904 --- /dev/null +++ b/scripts/test-signing-client.ps1 @@ -0,0 +1,47 @@ +#Requires -Version 7.2 +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest +if ($env:GITHUB_ACTIONS -ne 'true' -or $env:RUNNER_OS -ne 'Windows') { + throw 'Run this test only on a disposable Windows Actions runner.' +} +$setup = Join-Path $PSScriptRoot 'install-signing-client.ps1' + +# Reproduce the failed release with no registered PSGallery, then use the real service. +if (Get-PSRepository -Name PSGallery -ErrorAction SilentlyContinue) { + Unregister-PSRepository -Name PSGallery -ErrorAction Stop +} +if (Get-PSRepository -Name PSGallery -ErrorAction SilentlyContinue) { throw 'PSGallery was not removed for the test.' } +& $setup +$module = Get-Module -ListAvailable ArtifactSigning | Where-Object Version -eq ([version]'0.1.8') +if (-not $module) { throw 'The pinned module was not installed.' } +& $setup +Write-Output 'Real missing-repository recovery, module import, and repeated setup passed.' + +# Check bounded retries without waiting or making extra network requests. +& { + $state = @{ Attempts=0; Sleeps=0; Failures=1; Source='https://www.powershellgallery.com/api/v2' } + function Get-PSRepository { + [CmdletBinding()] param([string]$Name) + [pscustomobject]@{ Name='PSGallery'; SourceLocation=$state.Source } + } + function Install-Module { + [CmdletBinding()] param($Name, $RequiredVersion, $Scope, [switch]$Force, $Repository) + if ($Name -ne 'ArtifactSigning' -or $RequiredVersion -ne '0.1.8' -or $Scope -ne 'CurrentUser' -or $Repository -ne 'PSGallery') { + throw 'The installer changed its pinned module or repository.' + } + $state.Attempts++ + if ($state.Attempts -le $state.Failures) { throw 'Simulated package service failure.' } + } + function Start-Sleep { param($Seconds) $state.Sleeps++ } + & $setup + if ($state.Attempts -ne 2 -or $state.Sleeps -ne 1) { throw 'Transient failure recovery failed.' } + $state.Attempts=0; $state.Sleeps=0; $state.Failures=3 + $failed=$false + try { & $setup } catch { $failed=$true } + if (-not $failed -or $state.Attempts -ne 3 -or $state.Sleeps -ne 2) { throw 'Persistent failures did not stop after three attempts.' } + $state.Attempts=0; $state.Sleeps=0; $state.Source='https://packages.example.invalid/api/v2' + $failed=$false + try { & $setup } catch [System.Security.SecurityException] { $failed=$true } + if (-not $failed -or $state.Attempts -ne 0 -or $state.Sleeps -ne 0) { throw 'An unexpected package source was not rejected.' } +} +Write-Output 'Retry limits and package source checks passed.' From 0f71cae93be938b78850bb6a5d00800d4fd6743a Mon Sep 17 00:00:00 2001 From: Bjarn Bronsveld Date: Wed, 16 Sep 2026 01:02:05 +0200 Subject: [PATCH 2/2] fix(ci): report verified signing setup success explicitly --- .github/workflows/release.yml | 1 + scripts/install-signing-client.ps1 | 4 +++- scripts/test-signing-client.ps1 | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 77c79d4..1f65154 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -80,6 +80,7 @@ jobs: MACOS_SIGN_TEAM_ID: ${{ vars.MACOS_SIGN_TEAM_ID }} run: python scripts/release.py settings - name: Install the pinned Artifact Signing client + timeout-minutes: 5 if: steps.saved.outputs.restored != 'true' shell: pwsh run: ./scripts/install-signing-client.ps1 diff --git a/scripts/install-signing-client.ps1 b/scripts/install-signing-client.ps1 index 853ed66..f922a97 100644 --- a/scripts/install-signing-client.ps1 +++ b/scripts/install-signing-client.ps1 @@ -18,7 +18,9 @@ for ($attempt = 1; $attempt -le 3; $attempt++) { throw 'The pinned Artifact Signing client did not load.' } Write-Output 'ArtifactSigning 0.1.8 is installed and ready.' - return + # PackageManagement can leave a native NuGet error code after recovery. + # Report success only after the exact module and command have loaded. + exit 0 } catch [System.Security.SecurityException] { throw } catch { diff --git a/scripts/test-signing-client.ps1 b/scripts/test-signing-client.ps1 index 4485904..3f26a30 100644 --- a/scripts/test-signing-client.ps1 +++ b/scripts/test-signing-client.ps1 @@ -11,7 +11,9 @@ if (Get-PSRepository -Name PSGallery -ErrorAction SilentlyContinue) { Unregister-PSRepository -Name PSGallery -ErrorAction Stop } if (Get-PSRepository -Name PSGallery -ErrorAction SilentlyContinue) { throw 'PSGallery was not removed for the test.' } +$global:LASTEXITCODE = 23 & $setup +if ($LASTEXITCODE -ne 0) { throw 'Successful setup kept a stale native process exit code.' } $module = Get-Module -ListAvailable ArtifactSigning | Where-Object Version -eq ([version]'0.1.8') if (-not $module) { throw 'The pinned module was not installed.' } & $setup