mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-08-11 00:30:12 -04:00
182 lines
7.7 KiB
YAML
182 lines
7.7 KiB
YAML
name: Windows Installer
|
|
|
|
# Build the Windows installer .exe.
|
|
#
|
|
# Triggers:
|
|
# - Tag push matching v* (release builds, uploaded as a release asset)
|
|
# - Manual dispatch (for testing the build pipeline)
|
|
#
|
|
# Release tags are Authenticode-signed through the SignPath Foundation OSS
|
|
# program. Daily prereleases are deliberately left unsigned so they don't burn
|
|
# the OSS signing quota; use the `sign` dispatch input to exercise the signing
|
|
# path by hand.
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- 'v*'
|
|
workflow_dispatch:
|
|
inputs:
|
|
sign:
|
|
description: 'Submit the installer to SignPath for signing'
|
|
type: boolean
|
|
default: false
|
|
|
|
# Least-privilege per CodeQL actions/missing-workflow-permissions.
|
|
# contents: write is required by softprops/action-gh-release to attach
|
|
# the .exe to a tag release; the manual-dispatch path doesn't trigger
|
|
# that step and could run with read-only, but a single workflow-level
|
|
# block keeps the surface auditable in one place.
|
|
# actions: read lets the SignPath connector download the uploaded artifact
|
|
# through the API. Declaring a permissions block at all drops every scope we
|
|
# don't name to `none`, so the signing step fails to fetch the artifact
|
|
# without it.
|
|
permissions:
|
|
contents: write
|
|
actions: read
|
|
|
|
jobs:
|
|
build:
|
|
runs-on: windows-latest
|
|
timeout-minutes: 30
|
|
|
|
env:
|
|
# Sign real release tags but not `-daily.` prereleases, and let a manual
|
|
# run opt in. GitHub's `||` returns the *last* operand when everything is
|
|
# falsy (an empty string here, not `false`), so every use site compares
|
|
# against the string 'true' rather than treating this as a boolean.
|
|
SIGN: ${{ (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '-daily.')) || inputs.sign }}
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Python
|
|
uses: actions/setup-python@v5
|
|
with:
|
|
python-version: '3.13'
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '22'
|
|
|
|
# Inno Setup 6.x is pre-installed on windows-latest runners (under
|
|
# C:\Program Files (x86)\Inno Setup 6\). No install step needed.
|
|
|
|
- name: Stage installer artifacts
|
|
working-directory: installers/windows
|
|
run: python build.py
|
|
shell: pwsh
|
|
|
|
- name: Compile installer (ISCC)
|
|
working-directory: installers/windows
|
|
run: |
|
|
& "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" bambuddy.iss
|
|
shell: pwsh
|
|
|
|
# SignPath signs a *GitHub artifact*, not a workspace path: the connector
|
|
# pulls the artifact back out through the API, which is why this upload
|
|
# has to happen before signing and why upload-artifact must be v4 or newer
|
|
# (older versions expose no `artifact-id` output). Kept as a separate,
|
|
# clearly-named artifact so an unsigned build is never mistaken for a
|
|
# signed one when downloading from the run page.
|
|
- name: Upload unsigned installer
|
|
id: upload_unsigned
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: bambuddy-windows-installer-unsigned
|
|
path: installers/windows/build/output/*.exe
|
|
if-no-files-found: error
|
|
|
|
# The artifact arrives at SignPath as a .zip (that is simply what
|
|
# upload-artifact produces), so the artifact configuration on the SignPath
|
|
# side describes a <zip-file> wrapping the <pe-file>. With skip-decompress
|
|
# left at its default the signed archive is extracted again here, so
|
|
# `signed/` ends up holding the bare .exe.
|
|
- name: Sign installer (SignPath)
|
|
if: env.SIGN == 'true'
|
|
uses: signpath/github-action-submit-signing-request@v2
|
|
with:
|
|
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
|
|
# Not a credential -- the organization ID appears in ordinary SignPath
|
|
# URLs and is useless without the API token above.
|
|
organization-id: '4d7e5b59-d0fb-4a6b-b385-b861e18c6386'
|
|
project-slug: 'bambuddy'
|
|
signing-policy-slug: 'test-signing'
|
|
github-artifact-id: ${{ steps.upload_unsigned.outputs.artifact-id }}
|
|
wait-for-completion: true
|
|
output-artifact-directory: installers/windows/build/signed
|
|
|
|
# Replace the unsigned binary in-place so every downstream step (alias,
|
|
# artifact upload, release attachment) keeps working off one directory and
|
|
# cannot accidentally publish the unsigned copy.
|
|
- name: Promote signed installer
|
|
if: env.SIGN == 'true'
|
|
shell: pwsh
|
|
working-directory: installers/windows/build
|
|
run: |
|
|
$signed = @(Get-ChildItem -Path signed -Filter *.exe)
|
|
if ($signed.Count -ne 1) {
|
|
throw "expected exactly one signed .exe, found $($signed.Count)"
|
|
}
|
|
Move-Item -Force $signed[0].FullName (Join-Path output $signed[0].Name)
|
|
Write-Host "promoted signed installer: $($signed[0].Name)"
|
|
|
|
# Fail loudly rather than shipping an unsigned .exe under a signed
|
|
# release. The test certificate is self-signed, so Windows reports the
|
|
# signature as untrusted (`UnknownError`) -- that is expected and is not
|
|
# what this checks. Only the absence of a signature is treated as a
|
|
# failure; swap in a stricter assertion once the production certificate
|
|
# is imported.
|
|
- name: Verify signature
|
|
if: env.SIGN == 'true'
|
|
shell: pwsh
|
|
working-directory: installers/windows/build/output
|
|
run: |
|
|
Get-ChildItem -Filter *.exe | ForEach-Object {
|
|
$sig = Get-AuthenticodeSignature $_.FullName
|
|
if ($sig.Status -eq 'NotSigned') {
|
|
throw "$($_.Name) carries no Authenticode signature"
|
|
}
|
|
Write-Host "$($_.Name): $($sig.Status) / $($sig.SignerCertificate.Subject)"
|
|
}
|
|
|
|
# Stable + beta tag releases (e.g. v0.2.5b1, v0.3.0) get an unversioned
|
|
# copy alongside the versioned filename so external surfaces (website,
|
|
# wiki, newsletters) can link to a stable URL that survives version
|
|
# bumps:
|
|
#
|
|
# https://github.com/maziggy/bambuddy/releases/latest/download/bambuddy-windows-x64-setup.exe
|
|
#
|
|
# GitHub's `latest` redirect excludes prereleases, so this URL always
|
|
# points at whatever was released as a full release. Daily prereleases
|
|
# are excluded from the alias because (a) the unversioned name would be
|
|
# semantically confusing next to the date-stamped versioned name on a
|
|
# daily prerelease page, and (b) there's no stable "latest daily" URL
|
|
# anyway (`latest` skips prereleases), so the alias adds no value there.
|
|
#
|
|
# Runs after signing so the alias is a copy of the *signed* binary.
|
|
- name: Create unversioned alias (non-daily tags only)
|
|
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '-daily.')
|
|
shell: pwsh
|
|
working-directory: installers/windows/build/output
|
|
run: |
|
|
$versioned = Get-ChildItem -Filter "bambuddy-*-windows-x64-setup.exe" | Select-Object -First 1
|
|
if (-not $versioned) { throw "no versioned installer .exe found" }
|
|
Copy-Item $versioned.FullName "bambuddy-windows-x64-setup.exe"
|
|
Write-Host "alias: bambuddy-windows-x64-setup.exe -> $($versioned.Name)"
|
|
|
|
- name: Upload installer artifact
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: bambuddy-windows-installer
|
|
path: installers/windows/build/output/*.exe
|
|
if-no-files-found: error
|
|
|
|
- name: Attach installer to release
|
|
if: startsWith(github.ref, 'refs/tags/v')
|
|
uses: softprops/action-gh-release@v2
|
|
with:
|
|
files: installers/windows/build/output/*.exe
|
|
fail_on_unmatched_files: true
|