mirror of
https://github.com/ddeverill/SteamlessController.git
synced 2026-10-18 20:59:45 -05:00
Build and sign releases through SignPath on GitHub Actions
SignPath Foundation signs only what a trusted build system built from the repository, so releases move from local builds to a workflow. A tag push builds, signs the three executables, compiles the installer around the signed copies, signs the installer, and opens a draft release. A manual run does the same under test-signing, without the release. The README gains the code signing policy the Foundation requires, including a privacy note for the Google Fonts request the remap window makes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
19e5735504
commit
1a1253a384
4 files changed
+246
No files matched your search
@@ -0,0 +1,188 @@
|
||||
# Builds, signs and packages a release.
|
||||
#
|
||||
# SignPath Foundation signs only what a trusted build system built from this
|
||||
# repository, so this workflow is the only way a signed build can exist. A
|
||||
# local `cmake --build --preset release` + Inno compile still works for
|
||||
# testing, but what it produces cannot be signed.
|
||||
#
|
||||
# Signing happens in two rounds, because the installer contains the
|
||||
# executables and so has to be compiled around the signed copies:
|
||||
#
|
||||
# 1. build -> sign the three .exes (artifact configuration "exes")
|
||||
# 2. compile the installer around them -> sign Setup.exe ("installer")
|
||||
#
|
||||
# Under release-signing, each round waits for a manual approval in SignPath,
|
||||
# so a release takes two approvals. test-signing needs none.
|
||||
#
|
||||
# Triggers:
|
||||
# * a pushed tag vX.Y -> release-signing, then a DRAFT GitHub release
|
||||
# with the signed installer attached
|
||||
# * Run workflow (manual) -> test-signing by default; the installer is
|
||||
# left as a workflow artifact, no release made
|
||||
#
|
||||
# Configuration, under Settings > Secrets and variables > Actions:
|
||||
# secret SIGNPATH_API_TOKEN API token of the SignPath CI user
|
||||
# variable SIGNPATH_ORGANIZATION_ID
|
||||
# variable SIGNPATH_PROJECT_SLUG
|
||||
#
|
||||
# Artifact configurations must exist in SignPath with the slugs "exes" and
|
||||
# "installer"; their contents are versioned in .signpath/.
|
||||
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ['v*']
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
signing-policy:
|
||||
description: SignPath signing policy
|
||||
type: choice
|
||||
options: [test-signing, release-signing]
|
||||
default: test-signing
|
||||
|
||||
permissions:
|
||||
contents: write # create the draft release
|
||||
actions: read # lets SignPath fetch the uploaded artifacts
|
||||
|
||||
env:
|
||||
SIGNING_POLICY: ${{ startsWith(github.ref, 'refs/tags/') && 'release-signing' || inputs.signing-policy || 'test-signing' }}
|
||||
BUILD_DIR: build\release\Release
|
||||
VIGEM_FILE: ViGEmBus_1.22.0_x64_x86_arm64.exe
|
||||
VIGEM_URL: https://github.com/nefarius/ViGEmBus/releases/download/v1.22.0/ViGEmBus_1.22.0_x64_x86_arm64.exe
|
||||
VIGEM_SHA256: 89220a7865076b342892f98865f3499fb7c4cfd673159e89d352c360fd014c6a
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: windows-latest
|
||||
# Covers two waits for a human to approve in SignPath.
|
||||
timeout-minutes: 300
|
||||
defaults:
|
||||
run:
|
||||
shell: pwsh
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
# A tag that disagrees with the script would publish an installer under
|
||||
# one version that installs another. The script already refuses an exe
|
||||
# whose version differs from its own; this closes the other side.
|
||||
- name: Check tag matches installer version
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
run: |
|
||||
$iss = Get-Content resources\InnoInstallerScript.iss -Raw
|
||||
if ($iss -notmatch '#define MyAppVersion "([^"]+)"') { throw 'MyAppVersion not found in the installer script' }
|
||||
$scriptVersion = $Matches[1]
|
||||
$tagVersion = '${{ github.ref_name }}'.TrimStart('v')
|
||||
if ($tagVersion -ne $scriptVersion) {
|
||||
throw "Tag ${{ github.ref_name }} does not match MyAppVersion $scriptVersion"
|
||||
}
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cmake --preset release
|
||||
cmake --build --preset release
|
||||
|
||||
# ---- Round one: the executables -------------------------------------
|
||||
|
||||
- name: Stage unsigned executables
|
||||
run: |
|
||||
New-Item -ItemType Directory staging\exes | Out-Null
|
||||
foreach ($exe in 'SteamlessController.exe', 'SteamlessDeviceCycle.exe', 'ViGEmBusProbe.exe') {
|
||||
Copy-Item "$env:BUILD_DIR\$exe" staging\exes\
|
||||
}
|
||||
|
||||
- name: Upload unsigned executables
|
||||
id: upload-exes
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: unsigned-exes
|
||||
path: staging\exes\
|
||||
|
||||
- name: Sign executables
|
||||
uses: signpath/github-action-submit-signing-request@v3
|
||||
with:
|
||||
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
|
||||
organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }}
|
||||
project-slug: ${{ vars.SIGNPATH_PROJECT_SLUG }}
|
||||
signing-policy-slug: ${{ env.SIGNING_POLICY }}
|
||||
artifact-configuration-slug: exes
|
||||
github-artifact-id: ${{ steps.upload-exes.outputs.artifact-id }}
|
||||
wait-for-completion: true
|
||||
wait-for-completion-timeout-in-seconds: 7200
|
||||
output-artifact-directory: signed\exes
|
||||
|
||||
# Inno reads from BUILD_DIR, so the signed copies replace the unsigned
|
||||
# ones there rather than the script learning a second source directory.
|
||||
- name: Put signed executables in place
|
||||
run: Copy-Item signed\exes\*.exe $env:BUILD_DIR\ -Force
|
||||
|
||||
# ---- Round two: the installer ---------------------------------------
|
||||
|
||||
# Not in the repository (*.exe is gitignored). Pinned by hash so the
|
||||
# installer can only ever carry the exact binary nefarius published.
|
||||
- name: Fetch ViGEmBus installer
|
||||
run: |
|
||||
Invoke-WebRequest $env:VIGEM_URL -OutFile "resources\$env:VIGEM_FILE"
|
||||
$hash = (Get-FileHash "resources\$env:VIGEM_FILE" -Algorithm SHA256).Hash
|
||||
if ($hash -ne $env:VIGEM_SHA256) { throw "ViGEmBus hash mismatch: got $hash" }
|
||||
|
||||
- name: Compile installer
|
||||
run: |
|
||||
$iscc = "${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe"
|
||||
if (-not (Test-Path $iscc)) {
|
||||
choco install innosetup -y --no-progress
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Inno Setup install failed' }
|
||||
}
|
||||
& $iscc resources\InnoInstallerScript.iss
|
||||
if ($LASTEXITCODE -ne 0) { throw "ISCC failed with exit code $LASTEXITCODE" }
|
||||
|
||||
- name: Upload unsigned installer
|
||||
id: upload-installer
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: unsigned-installer
|
||||
path: Output\SteamlessController-Setup.exe
|
||||
|
||||
- name: Sign installer
|
||||
uses: signpath/github-action-submit-signing-request@v3
|
||||
with:
|
||||
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
|
||||
organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }}
|
||||
project-slug: ${{ vars.SIGNPATH_PROJECT_SLUG }}
|
||||
signing-policy-slug: ${{ env.SIGNING_POLICY }}
|
||||
artifact-configuration-slug: installer
|
||||
github-artifact-id: ${{ steps.upload-installer.outputs.artifact-id }}
|
||||
wait-for-completion: true
|
||||
wait-for-completion-timeout-in-seconds: 7200
|
||||
output-artifact-directory: signed\installer
|
||||
|
||||
# Test certificates do not chain to a trusted root, so only a
|
||||
# release-signed build can be held to this.
|
||||
- name: Verify signatures
|
||||
if: env.SIGNING_POLICY == 'release-signing'
|
||||
run: |
|
||||
$files = @(Get-ChildItem signed\exes\*.exe) + @(Get-Item signed\installer\SteamlessController-Setup.exe)
|
||||
foreach ($file in $files) {
|
||||
$sig = Get-AuthenticodeSignature $file.FullName
|
||||
"{0}: {1} ({2})" -f $file.Name, $sig.Status, $sig.SignerCertificate.Subject
|
||||
if ($sig.Status -ne 'Valid') { throw "$($file.Name) is not validly signed" }
|
||||
}
|
||||
|
||||
- name: Upload signed installer
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: SteamlessController-Setup-${{ env.SIGNING_POLICY }}
|
||||
path: signed\installer\SteamlessController-Setup.exe
|
||||
|
||||
# A draft, so the notes get written and the installer tried before
|
||||
# anyone can download it.
|
||||
- name: Create draft release
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
gh release create '${{ github.ref_name }}' signed\installer\SteamlessController-Setup.exe `
|
||||
--draft --verify-tag `
|
||||
--title 'SteamlessController-${{ github.ref_name }}' `
|
||||
--notes 'Release notes to follow.'
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Round one of a release: the three executables the installer packages.
|
||||
|
||||
The file of record is the copy pasted into SignPath's web UI under the
|
||||
project's artifact configuration with slug "exes". This copy is here so the
|
||||
configuration is reviewed and versioned alongside the workflow that submits
|
||||
to it. Change both together.
|
||||
|
||||
The ViGEmBus installer is deliberately absent. It is nefarius's binary,
|
||||
already signed by them, and never passes through SignPath.
|
||||
-->
|
||||
<artifact-configuration xmlns="http://signpath.io/artifact-configuration/v1">
|
||||
<zip-file>
|
||||
<pe-file path="SteamlessController.exe">
|
||||
<authenticode-sign/>
|
||||
</pe-file>
|
||||
<pe-file path="SteamlessDeviceCycle.exe">
|
||||
<authenticode-sign/>
|
||||
</pe-file>
|
||||
<pe-file path="ViGEmBusProbe.exe">
|
||||
<authenticode-sign/>
|
||||
</pe-file>
|
||||
</zip-file>
|
||||
</artifact-configuration>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Round two of a release: the Inno Setup installer, compiled around the
|
||||
executables signed in round one (see exes.xml).
|
||||
|
||||
The file of record is the copy in SignPath's web UI under the project's
|
||||
artifact configuration with slug "installer". Change both together.
|
||||
-->
|
||||
<artifact-configuration xmlns="http://signpath.io/artifact-configuration/v1">
|
||||
<zip-file>
|
||||
<pe-file path="SteamlessController-Setup.exe">
|
||||
<authenticode-sign/>
|
||||
</pe-file>
|
||||
</zip-file>
|
||||
</artifact-configuration>
|
||||
@@ -113,3 +113,21 @@ saved identity can take.
|
||||
## Third-party
|
||||
|
||||
- [ViGEmClient](https://github.com/nefarius/ViGEmClient) — MIT License, built from source as a static library
|
||||
|
||||
## Code signing policy
|
||||
|
||||
Free code signing provided by [SignPath.io](https://about.signpath.io), certificate by [SignPath Foundation](https://signpath.org).
|
||||
|
||||
Releases are built and signed by the [Release workflow](.github/workflows/release.yml) from this repository's source. Only this project's own executables and installer are signed. The bundled ViGEmBus installer is signed by its own publisher and passes through unchanged.
|
||||
|
||||
Team roles:
|
||||
|
||||
- Committers and reviewers: [Dylan Deverill](https://github.com/ddeverill)
|
||||
- Approvers: [Dylan Deverill](https://github.com/ddeverill)
|
||||
|
||||
### Privacy
|
||||
|
||||
SteamlessController sends no data about you or your system anywhere. It makes two kinds of network request:
|
||||
|
||||
- The controller remapping window loads its fonts from Google Fonts, which receives the usual details of a web request, such as your IP address.
|
||||
- If the ViGEmBus driver is missing, clicking the notification that says so opens its download page in your browser.
|
||||
Reference in new issue
Block a user