Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 67 additions & 3 deletions .github/workflows/build-desktop-platforms.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ env:
jobs:
build-windows:
runs-on: windows-latest
outputs:
windows-artifact-id: ${{ steps.upload-windows.outputs.artifact-id }}

steps:
- name: Checkout code
Expand Down Expand Up @@ -62,6 +64,7 @@ jobs:
shell: bash

- name: Upload Windows installers
id: upload-windows
uses: actions/upload-artifact@v4
with:
name: windows-installers
Expand All @@ -72,6 +75,65 @@ jobs:
retention-days: 30
compression-level: 6

sign-windows:
name: Sign Windows installers (SignPath)
needs: build-windows
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read

steps:
# Fail loudly if any SignPath config is missing instead of letting the
# action surface a generic auth/lookup error halfway through the run.
# Policy slug lives in a repo variable so swapping test-signing →
# release-signing once SignPath issues the prod cert is a one-click
# change in repo Settings, with no code edit needed.
- name: Verify SignPath configuration
env:
ORG_ID: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
API_TOKEN: ${{ secrets.SIGNPATH_API_TOKEN }}
POLICY_SLUG: ${{ vars.SIGNPATH_SIGNING_POLICY_SLUG }}
run: |
set -euo pipefail
missing=()
[ -n "${ORG_ID:-}" ] || missing+=("SIGNPATH_ORGANIZATION_ID (secret)")
[ -n "${API_TOKEN:-}" ] || missing+=("SIGNPATH_API_TOKEN (secret)")
[ -n "${POLICY_SLUG:-}" ] || missing+=("SIGNPATH_SIGNING_POLICY_SLUG (variable)")
if [ ${#missing[@]} -gt 0 ]; then
echo "::error::Missing SignPath configuration:"
printf '::error:: - %s\n' "${missing[@]}"
exit 1
fi
echo "Signing policy: ${POLICY_SLUG}"
shell: bash

# Pinned to the v2 release commit instead of @v2 so a force-push to the
# mutable tag (or a compromise of the SignPath org) cannot inject code
# into a job that holds id-token: write and the SignPath API token.
# When bumping, look up the new SHA via:
# gh api repos/signpath/github-action-submit-signing-request/git/refs/tags/<tag>
- name: Submit signing request
uses: signpath/github-action-submit-signing-request@b9d91eadd323de506c0c81cf0c7fe7438f3360fd # v2 (2025-10-23)
with:
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
project-slug: 'GitHub-Store'
signing-policy-slug: ${{ vars.SIGNPATH_SIGNING_POLICY_SLUG }}
artifact-configuration-slug: 'initial-version'
github-artifact-id: ${{ needs.build-windows.outputs.windows-artifact-id }}
wait-for-completion: true
output-artifact-directory: signed-artifacts

- name: Upload signed Windows installers
uses: actions/upload-artifact@v4
with:
name: windows-installers-signed
path: signed-artifacts/*
if-no-files-found: error
retention-days: 30
compression-level: 0

build-macos:
strategy:
matrix:
Expand Down Expand Up @@ -417,7 +479,7 @@ jobs:

release:
name: Draft release with all installers
needs: [build-windows, build-macos, build-linux]
needs: [sign-windows, build-macos, build-linux]
runs-on: ubuntu-latest
permissions:
contents: write
Expand Down Expand Up @@ -482,11 +544,13 @@ jobs:
linux_appimage_count=0
linux_arch_count=0

# Windows — names already unique (.exe, .msi)
# Windows — names already unique (.exe, .msi). Files come from the
# signed artifact, not the raw build output, so they carry the
# SignPath Authenticode signature.
while IFS= read -r f; do
[ -n "$f" ] || continue
stage "$f" "$(basename "$f")" && windows_count=$((windows_count + 1)) || true
done < <(find artifacts/windows-installers -type f \( -name '*.exe' -o -name '*.msi' \) 2>/dev/null)
done < <(find artifacts/windows-installers-signed -type f \( -name '*.exe' -o -name '*.msi' \) 2>/dev/null)

# macOS — disambiguate x64 vs arm64 (Compose outputs identical filenames per arch)
while IFS= read -r f; do
Expand Down