Skip to content
Merged
Show file tree
Hide file tree
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
120 changes: 11 additions & 109 deletions .github/workflows/windows-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,126 +3,40 @@ name: Windows Release
on:
release:
types: [published]
# Dry-run entry point: builds and signs without publishing anything, so the SignPath
# integration can be exercised against the test-signing policy before a real tag is cut.
workflow_dispatch:
inputs:
signing_policy:
description: SignPath signing policy slug to submit against
required: false
default: test-signing

permissions: {}

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
# Source the tag from the github context (evaluated by Actions) rather than
# $env:GITHUB_REF_NAME, which is only injected by runner >= 2.290. Keeps the
# build working on older self-hosted runners. (#212 follow-up)
# workflow_dispatch runs off a branch, so there is no vX.Y.Z tag to derive a version from.
PACKAGE_VERSION: ${{ github.event_name == 'release' && github.ref_name || '0.0.0-dev' }}

jobs:
# SignPath's OSS tier requires every job leading up to a signing request to run on a
# GitHub-hosted agent, so the executable is built here rather than on the self-hosted runner
# that packages it. This job builds nothing but StemDeck.exe; the heavy Python/torch bundling
# stays downstream.
sign-exe:
runs-on: windows-latest
timeout-minutes: 45
permissions:
contents: read
actions: read # the SignPath action reads job details and downloads the artifact
defaults:
run:
shell: powershell
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

# Must run before the build: SignPath restricts Foundation projects on PE metadata, and
# Tauri stamps ProductName/FileVersion into the executable from these files.
- name: write version files
run: |
$version = $env:PACKAGE_VERSION -replace '^v', ''
if (-not $version) { throw "PACKAGE_VERSION is not set" }
(Get-Content "desktop/src-tauri/Cargo.toml") -replace '^version = ".*"', "version = `"$version`"" |
Set-Content "desktop/src-tauri/Cargo.toml"
(Get-Content "desktop/src-tauri/tauri.conf.json") -replace '"version": "[^"]*"', "`"version`": `"$version`"" |
Set-Content "desktop/src-tauri/tauri.conf.json"
Write-Host "Building StemDeck.exe version $version"

- name: build executable
working-directory: desktop
env:
CI: "true" # Tauri only accepts true/false here
run: |
rustup default stable
npm ci --include=dev
node node_modules/@tauri-apps/cli/tauri.js build

# The SignPath connector only accepts artifacts stored on the GitHub server.
- name: stage unsigned executable
run: |
New-Item -ItemType Directory -Force unsigned | Out-Null
Copy-Item -Force desktop/src-tauri/target/release/stemdeck.exe unsigned/

- name: upload unsigned executable
id: upload-unsigned
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: stemdeck-exe-unsigned
path: unsigned/stemdeck.exe

- name: submit signing request
uses: signpath/github-action-submit-signing-request@c92b958760219087e01f8d67a1669ed57afe2627 # v2.3
with:
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }}
project-slug: stemdeck
signing-policy-slug: ${{ github.event_name == 'release' && 'release-signing' || inputs.signing_policy }}
github-artifact-id: ${{ steps.upload-unsigned.outputs.artifact-id }}
wait-for-completion: true
# Release signing needs a human approval in the SignPath console; the 600s default
# expires long before an approver is likely to see the mail.
wait-for-completion-timeout-in-seconds: 3600
output-artifact-directory: signed

- name: upload signed executable
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: stemdeck-exe-signed
path: signed/stemdeck.exe

build-and-upload:
needs: sign-exe
# Runner must be windows/x64 with PowerShell, Docker, and rustup.
runs-on: [self-hosted, windows, x64]
timeout-minutes: 90
permissions:
contents: write
# Source the tag from the github context (evaluated by Actions) rather than
# $env:GITHUB_REF_NAME, which is only injected by runner >= 2.290. Keeps the
# build working on older self-hosted runners. (#212 follow-up)
env:
REF_NAME: ${{ github.ref_name }}
defaults:
run:
shell: powershell
steps:
- name: clean workspace
run: |
Remove-Item -Recurse -Force .build, dist, signed -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force .build, dist -ErrorAction SilentlyContinue

- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: download signed executable
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: stemdeck-exe-signed
path: signed

- name: write version files
run: |
$version = $env:PACKAGE_VERSION -replace '^v', ''
if (-not $version) { throw "PACKAGE_VERSION is not set" }
$tag = $env:REF_NAME
if (-not $tag) { throw "REF_NAME is not set" }
$version = $tag -replace '^v', ''
$json = "{`"version`": `"$version`"}"
Set-Content -Path "static/version.json" -Value $json -Encoding UTF8
(Get-Content "desktop/src-tauri/Cargo.toml") -replace '^version = ".*"', "version = `"$version`"" |
Expand All @@ -135,22 +49,18 @@ jobs:
Set-Content "desktop/package.json"
Write-Host "Wrote version $version to all version files"

# Both variants package the one signed executable from sign-exe, so the Rust build is not
# repeated here.
- name: build Windows NVIDIA
run: |
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/windows/make-portable.ps1 `
-PackageName StemDeck-Windows-x64.NVIDIA `
-PackageVersion "$env:PACKAGE_VERSION" `
-PrebuiltExe "$PWD\signed\stemdeck.exe" `
-PackageVersion "$env:REF_NAME" `
-StripVenv

- name: build Windows CPU
run: |
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/windows/make-portable.ps1 `
-PackageName StemDeck-Windows-x64 `
-PackageVersion "$env:PACKAGE_VERSION" `
-PrebuiltExe "$PWD\signed\stemdeck.exe" `
-PackageVersion "$env:REF_NAME" `
-CpuOnly `
-StripVenv

Expand All @@ -172,13 +82,6 @@ jobs:
Write-Host " $($hash.Hash) $($_.Name)"
}

Write-Host "Authenticode signatures on the packaged executables:"
Get-ChildItem -Path "dist" -Filter "StemDeck.exe" -Recurse -File |
ForEach-Object {
$sig = Get-AuthenticodeSignature -LiteralPath $_.FullName
Write-Host " $($_.FullName): $($sig.Status) $($sig.SignerCertificate.Subject)"
}

Write-Host "Pulling latest ClamAV scanner image..."
docker pull clamav/clamav:latest

Expand All @@ -191,7 +94,6 @@ jobs:
Write-Host "ClamAV scan completed successfully. No infected files reported."

- name: upload artifacts
if: github.event_name == 'release'
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
files: |
Expand Down
9 changes: 0 additions & 9 deletions .signpath/policies/stemdeck/release-signing.yml

This file was deleted.

33 changes: 0 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -402,39 +402,6 @@ The author(s) of StemDeck provide this software "as is", without warranty of any

---

## Code signing policy

Windows releases of StemDeck are Authenticode-signed. Free code signing is provided by
[SignPath.io](https://signpath.io/), certificate by [SignPath Foundation](https://signpath.org/).

The signed binary is `StemDeck.exe` inside `StemDeck-Windows-x64.zip` and
`StemDeck-Windows-x64.NVIDIA.zip`. The zip archives themselves are not signed; verify them with
the `.sha256` file published alongside each release asset. macOS and Linux builds are not
currently signed.

**Team roles**

| Role | Members |
|---|---|
| Committers | Thales Pereira ([@thcp](https://github.com/thcp)) |
| Reviewers | Thales Pereira ([@thcp](https://github.com/thcp)) |
| Approvers | Thales Pereira ([@thcp](https://github.com/thcp)) |

**How signing works**

The executable is built from this repository by the
[Windows Release workflow](.github/workflows/windows-release.yml) on a GitHub-hosted runner,
uploaded as a workflow artifact, and submitted to SignPath for signing. SignPath verifies the
build's origin (repository, branch, commit, and build job) before the certificate is applied.
Every release signing request is approved manually by an approver listed above.

**Privacy**

StemDeck does not transmit any personal data. All audio processing happens on the user's own
machine. See the [Disclaimer](#disclaimer) for details.

---

## Community

| Platform | Link |
Expand Down
7 changes: 0 additions & 7 deletions packaging/windows/README-WINDOWS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,6 @@ Run
2. Double-click StemDeck.exe.
3. Let first-run setup prepare local runtime assets.

Code signing
------------

StemDeck.exe is Authenticode-signed. Free code signing provided by SignPath.io,
certificate by SignPath Foundation. The zip itself is not signed; verify it with
the .sha256 file published next to it on the release page.

Notes
-----

Expand Down
70 changes: 18 additions & 52 deletions scripts/windows/make-portable.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ param(
[string]$PackageName = "StemDeck-Windows-x64",
[string]$PackageVersion,
[switch]$SkipTauriBuild,
[string]$PrebuiltExe,
[switch]$CpuOnly,
[switch]$StripVenv
)
Expand All @@ -28,19 +27,6 @@ $DesktopDir = Join-Path $Root "desktop"
$TauriDir = Join-Path $DesktopDir "src-tauri"
$TargetExe = Join-Path $TauriDir "target\$Configuration\stemdeck.exe"

# -PrebuiltExe packages an executable produced (and signed) by an earlier job instead of
# building one here. The release pipeline uses it so both the CPU and NVIDIA packages ship the
# one SignPath-signed binary rather than rebuilding Rust twice.
if ($PrebuiltExe) {
if ($SkipTauriBuild) {
throw "-PrebuiltExe and -SkipTauriBuild are mutually exclusive."
}
if (-not (Test-Path -LiteralPath $PrebuiltExe)) {
throw "Prebuilt executable not found: $PrebuiltExe"
}
$TargetExe = (Resolve-Path -LiteralPath $PrebuiltExe).Path
}

function Require-Command([string]$Name) {
if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) {
throw "Required command not found on PATH: $Name"
Expand Down Expand Up @@ -158,11 +144,9 @@ Remove -SkipTauriBuild or run the NVIDIA package build first so the CPU package
}
}

if (-not $PrebuiltExe) {
Require-Command "node"
Require-Command "npm"
Require-Command "cargo"
}
Require-Command "node"
Require-Command "npm"
Require-Command "cargo"

if (-not (Get-Command "py" -ErrorAction SilentlyContinue) -and -not (Get-Command "python" -ErrorAction SilentlyContinue)) {
throw "Python launcher not found. Install Python 3.12 on the Windows build agent."
Expand Down Expand Up @@ -253,47 +237,29 @@ if ($StripVenv) {
Remove-Item -Force
}

if (-not $PrebuiltExe) {
Push-Location $DesktopDir
try {
if (Test-Path "package-lock.json") {
npm ci --include=dev
} else {
npm install --include=dev
}
Push-Location $DesktopDir
try {
if (Test-Path "package-lock.json") {
npm ci --include=dev
} else {
npm install --include=dev
}

if (-not $SkipTauriBuild) {
$env:CI = "true" # Woodpecker sets CI=woodpecker; Tauri only accepts true/false
rustup default stable
Invoke-TauriBuild
} else {
Assert-Fresh-TauriBuild
}
} finally {
Pop-Location
if (-not $SkipTauriBuild) {
$env:CI = "true" # Woodpecker sets CI=woodpecker; Tauri only accepts true/false
rustup default stable
Invoke-TauriBuild
} else {
Assert-Fresh-TauriBuild
}
} finally {
Pop-Location
}

if (-not (Test-Path $TargetExe)) {
throw "Tauri executable not found at $TargetExe"
}

if ($PrebuiltExe) {
# Fail loudly if the signing job handed back an unsigned binary. A test-signing policy uses a
# self-signed certificate, so anything other than NotSigned is accepted here and only warned
# about; the release certificate yields Valid.
$Signature = Get-AuthenticodeSignature -LiteralPath $TargetExe
if ($Signature.Status -eq "NotSigned") {
throw "Prebuilt executable carries no Authenticode signature: $TargetExe"
}
if ($Signature.Status -ne "Valid") {
Write-Warning "Authenticode status is $($Signature.Status) (expected for a test-signing certificate)."
}
if ($Signature.SignerCertificate) {
Write-Host "Signed by : $($Signature.SignerCertificate.Subject)"
}
}

Copy-Item -Force $TargetExe (Join-Path $Stage "StemDeck.exe")

Compress-Archive -Path (Join-Path $Stage "*") -DestinationPath $ZipPath -Force
Expand Down
Loading