From 56b14afbcd1a876c5d5c7ffd34db8a99d26884f7 Mon Sep 17 00:00:00 2001 From: lx-s Date: Fri, 14 Aug 2026 13:07:55 +0200 Subject: [PATCH] Added github workflos for signpath --- .github/workflows/release.yml | 196 +++++++++++++++++++++++ .github/workflows/sign-release-asset.yml | 99 ++++++++++++ 2 files changed, 295 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/sign-release-asset.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..6bdf3da --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,196 @@ +name: Release + +# Builds WinMute.exe and the Inno Setup installer, signs both via SignPath, +# and (on a tagged push only) publishes them plus a zip as assets on a +# GitHub Release. +# +# Triggered by pushing a version tag matching the existing 4-part convention, +# e.g. `2.6.1.0`. Bump the version in WinMute/WinMute.rc and add an entry to +# Dist/bin/changelog.txt first, then: +# git tag 2.6.1.0 && git push origin 2.6.1.0 +# +# Can also be run manually (Actions tab -> Release -> Run workflow) to build +# and sign a one-off test build without tagging or creating a release - the +# signed exe/installer/zip are still uploaded as workflow artifacts so you +# can download and inspect them. +# +# Chocolatey packaging and the winget submission are NOT part of this +# workflow — run Dist/publish-packages.ps1 locally afterwards for those. +# +# Requires (repo Settings -> Secrets and variables -> Actions): +# secrets.SIGNPATH_API_TOKEN SignPath API token (Submitter role) +# vars.SIGNPATH_ORG_ID SignPath organization ID +# vars.SIGNPATH_PROJECT_SLUG SignPath project slug +# vars.SIGNPATH_SIGNING_POLICY_SLUG e.g. "test-signing" or "release-signing" +# vars.SIGNPATH_ARTIFACT_CONFIG_SLUG (optional) artifact configuration slug +# +# Also requires the SignPath GitHub App (https://github.com/apps/signpath) to be +# installed on this repository and linked as a Trusted Build System on the +# SignPath project, per https://docs.signpath.io/trusted-build-systems/github + +on: + push: + tags: + - '*.*.*.*' + workflow_dispatch: {} + +permissions: + contents: write + +jobs: + build-sign-release: + runs-on: windows-latest + defaults: + run: + shell: pwsh + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup MSBuild + uses: microsoft/setup-msbuild@v2 + + - name: Build WinMute.exe + run: msbuild WinMute\WinMute.vcxproj /p:Configuration=Release /p:Platform=x64 /m + + - name: Determine version + id: version + run: | + $version = (Get-Item Bin\WinMute.exe).VersionInfo.FileVersion.Trim() + if ('${{ github.event_name }}' -eq 'push') { + $tag = "${{ github.ref_name }}" + if ($version -ne $tag) { + throw "Built exe FileVersion '$version' does not match tag '$tag'. Did you forget to bump WinMute/WinMute.rc?" + } + } + "version=$version" >> $env:GITHUB_OUTPUT + + - name: Stage payload + run: | + New-Item -ItemType Directory -Force -Path Dist\bin\lang | Out-Null + Copy-Item Bin\WinMute.exe Dist\bin\WinMute.exe -Force + Copy-Item Translations\*.json Dist\bin\lang\ -Force + + - name: Upload unsigned WinMute.exe + id: upload-unsigned-exe + uses: actions/upload-artifact@v4 + with: + name: unsigned-WinMute.exe + path: Dist/bin/WinMute.exe + retention-days: 1 + + - name: Sign WinMute.exe + uses: signpath/github-action-submit-signing-request@v2 + with: + api-token: '${{ secrets.SIGNPATH_API_TOKEN }}' + organization-id: '${{ vars.SIGNPATH_ORG_ID }}' + project-slug: '${{ vars.SIGNPATH_PROJECT_SLUG }}' + signing-policy-slug: '${{ vars.SIGNPATH_SIGNING_POLICY_SLUG }}' + artifact-configuration-slug: '${{ vars.SIGNPATH_ARTIFACT_CONFIG_SLUG }}' + github-artifact-id: '${{ steps.upload-unsigned-exe.outputs.artifact-id }}' + wait-for-completion: true + output-artifact-directory: signed\exe + + - name: Replace exe with signed copy + run: Copy-Item signed\exe\WinMute.exe Dist\bin\WinMute.exe -Force + + - name: Install Inno Setup + run: | + $ProgressPreference = 'SilentlyContinue' + Invoke-WebRequest -Uri 'https://jrsoftware.org/download.php/is.exe' -OutFile "$env:RUNNER_TEMP\innosetup.exe" + Start-Process -FilePath "$env:RUNNER_TEMP\innosetup.exe" -ArgumentList '/VERYSILENT','/SUPPRESSMSGBOXES','/NORESTART','/SP-' -Wait + + $isccPath = Get-ChildItem "${env:ProgramFiles(x86)}" -Directory -Filter 'Inno Setup*' | + ForEach-Object { Join-Path $_.FullName 'ISCC.exe' } | + Where-Object { Test-Path $_ } | + Select-Object -First 1 + if (-not $isccPath) { throw 'Could not locate ISCC.exe after installing Inno Setup' } + "ISCC_PATH=$isccPath" >> $env:GITHUB_ENV + + # Latvian is a community ("unofficial") translation not bundled with + # Inno Setup itself; every other language referenced by + # Dist/InnoSetup/winmute-setup-languages.iss ships in the box. + $languagesDir = Join-Path (Split-Path $isccPath) 'Languages' + Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/jrsoftware/issrc/main/Files/Languages/Unofficial/Latvian.isl' -OutFile (Join-Path $languagesDir 'Latvian.isl') + + - name: Build installer + run: | + & "$env:ISCC_PATH" Dist\InnoSetup\winmute-setup.iss + + - name: Locate installer output + id: installer + run: | + # winmute-setup.iss names its output "WinMute-{#MyAppVersion}-Setup.exe", + # where MyAppVersion is read from the exe itself - already verified + # above to equal the tag on a tagged push - so discover the actual + # filename rather than assuming its exact form. + $setupFile = Get-ChildItem Dist\bin -Filter 'WinMute-*-Setup.exe' | Select-Object -First 1 + if (-not $setupFile) { throw 'Inno Setup did not produce a WinMute-*-Setup.exe output' } + "setupName=$($setupFile.Name)" >> $env:GITHUB_OUTPUT + + - name: Upload unsigned installer + id: upload-unsigned-setup + uses: actions/upload-artifact@v4 + with: + name: unsigned-WinMuteSetup.exe + path: Dist/bin/${{ steps.installer.outputs.setupName }} + retention-days: 1 + + - name: Sign WinMuteSetup.exe + uses: signpath/github-action-submit-signing-request@v2 + with: + api-token: '${{ secrets.SIGNPATH_API_TOKEN }}' + organization-id: '${{ vars.SIGNPATH_ORG_ID }}' + project-slug: '${{ vars.SIGNPATH_PROJECT_SLUG }}' + signing-policy-slug: '${{ vars.SIGNPATH_SIGNING_POLICY_SLUG }}' + artifact-configuration-slug: '${{ vars.SIGNPATH_ARTIFACT_CONFIG_SLUG }}' + github-artifact-id: '${{ steps.upload-unsigned-setup.outputs.artifact-id }}' + wait-for-completion: true + output-artifact-directory: signed\setup + + - name: Replace installer with signed copy + run: Copy-Item "signed\setup\${{ steps.installer.outputs.setupName }}" "Dist\bin\${{ steps.installer.outputs.setupName }}" -Force + + - name: Build zip + id: package + run: | + $zipName = "WinMute-${{ steps.version.outputs.version }}.zip" + $zipPath = "Dist\bin\$zipName" + if (Test-Path $zipPath) { Remove-Item $zipPath } + # Named members only - never the whole Dist\bin folder - so + # update-check.disabled and the Chocolatey-only WinMute.exe.gui + # marker never end up in the zip. + Compress-Archive -Path Dist\bin\WinMute.exe, Dist\bin\lang, Dist\bin\changelog.txt, Dist\bin\license.txt, Dist\bin\license.rtf -DestinationPath $zipPath + "zipName=$zipName" >> $env:GITHUB_OUTPUT + + - name: Upload signed artifacts (workflow record) + uses: actions/upload-artifact@v4 + with: + name: signed-WinMute-${{ steps.version.outputs.version }} + path: | + Dist/bin/WinMute.exe + Dist/bin/${{ steps.installer.outputs.setupName }} + Dist/bin/${{ steps.package.outputs.zipName }} + retention-days: 30 + + - name: Extract release notes + if: github.event_name == 'push' + run: | + $notesLines = @() + foreach ($line in Get-Content Dist\bin\changelog.txt) { + if ($line.Trim() -eq '' -and $notesLines.Count -gt 0) { break } + $notesLines += $line + } + Set-Content -Path release-notes.txt -Value ($notesLines -join "`n") + + - name: Create GitHub Release + if: github.event_name == 'push' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create "${{ steps.version.outputs.version }}" ` + "Dist\bin\WinMute.exe" ` + "Dist\bin\${{ steps.installer.outputs.setupName }}" ` + "Dist\bin\${{ steps.package.outputs.zipName }}" ` + --title "${{ steps.version.outputs.version }}" ` + --notes-file release-notes.txt diff --git a/.github/workflows/sign-release-asset.yml b/.github/workflows/sign-release-asset.yml new file mode 100644 index 0000000..0c8ae41 --- /dev/null +++ b/.github/workflows/sign-release-asset.yml @@ -0,0 +1,99 @@ +name: Sign release asset + +# Re-signs a file that is already attached to an EXISTING GitHub Release, in +# place, WITHOUT rebuilding it from source. +# +# For normal releases and one-off pipeline test builds, use the "Release" +# workflow (release.yml) instead - it builds, signs, and (on a tagged push) +# publishes WinMute.exe and the installer in one go, including a +# workflow_dispatch mode for test builds that doesn't touch any release. +# +# This workflow exists for the narrower case of re-signing bits that are +# already published - e.g. the SignPath signing policy or certificate +# changed and you want the already-shipped asset re-signed without cutting +# a whole new version. Usage: +# 1. Run this workflow (Actions tab -> Sign release asset -> Run workflow), +# giving it an existing release tag, the exact asset filename already +# attached to it, and the signing policy to use. +# 2. The signed file replaces that asset on the release, in place. +# +# Requires (see repo Settings -> Secrets and variables -> Actions): +# secrets.SIGNPATH_API_TOKEN SignPath API token (Submitter role) +# vars.SIGNPATH_ORG_ID SignPath organization ID +# vars.SIGNPATH_PROJECT_SLUG SignPath project slug +# vars.SIGNPATH_ARTIFACT_CONFIG_SLUG (optional) artifact configuration slug +# +# Also requires the SignPath GitHub App (https://github.com/apps/signpath) to be +# installed on this repository and linked as a Trusted Build System on the +# SignPath project, per https://docs.signpath.io/trusted-build-systems/github + +on: + workflow_dispatch: + inputs: + release_tag: + description: "Tag of the GitHub Release that holds the unsigned asset" + required: true + type: string + asset_name: + description: "Exact filename of the unsigned release asset to sign (e.g. WinMute.exe)" + required: true + type: string + signing_policy_slug: + description: "SignPath signing policy slug" + required: true + type: string + default: "test-signing" + +permissions: + contents: write + +jobs: + sign: + runs-on: ubuntu-latest + steps: + - name: Download unsigned asset from release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + mkdir -p unsigned + gh release download "${{ inputs.release_tag }}" \ + --repo "${{ github.repository }}" \ + --pattern "${{ inputs.asset_name }}" \ + --dir unsigned + + - name: Upload unsigned artifact for signing + id: upload-unsigned + uses: actions/upload-artifact@v4 + with: + name: unsigned-${{ inputs.asset_name }} + path: unsigned/${{ inputs.asset_name }} + retention-days: 1 + + - name: Submit signing request + id: sign + uses: signpath/github-action-submit-signing-request@v2 + with: + api-token: "${{ secrets.SIGNPATH_API_TOKEN }}" + organization-id: "${{ vars.SIGNPATH_ORG_ID }}" + project-slug: "${{ vars.SIGNPATH_PROJECT_SLUG }}" + signing-policy-slug: "${{ inputs.signing_policy_slug }}" + artifact-configuration-slug: "${{ vars.SIGNPATH_ARTIFACT_CONFIG_SLUG }}" + github-artifact-id: "${{ steps.upload-unsigned.outputs.artifact-id }}" + wait-for-completion: true + output-artifact-directory: signed + + - name: Upload signed artifact (workflow record) + uses: actions/upload-artifact@v4 + with: + name: signed-${{ inputs.asset_name }} + path: signed/${{ inputs.asset_name }} + retention-days: 30 + + - name: Replace unsigned asset on the release with the signed one + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release upload "${{ inputs.release_tag }}" \ + "signed/${{ inputs.asset_name }}" \ + --repo "${{ github.repository }}" \ + --clobber -- 2.43.0