Skip to content

Commit 941b4b1

Browse files
authored
chore: installer CI (#33)
1 parent f1c116a commit 941b4b1

File tree

3 files changed

+192
-30
lines changed

3 files changed

+192
-30
lines changed

.github/workflows/release.yaml

Lines changed: 68 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ on:
44
push:
55
tags:
66
- '*'
7+
workflow_dispatch:
8+
inputs:
9+
version:
10+
description: 'Version number (e.g. 1.2.3)'
11+
required: true
12+
default: '1.2.3'
713

814
permissions:
915
contents: write
@@ -20,42 +26,83 @@ jobs:
2026
with:
2127
dotnet-version: '8.0.x'
2228

29+
# Necessary for signing Windows binaries.
30+
- name: Setup Java
31+
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
32+
with:
33+
distribution: "zulu"
34+
java-version: "11.0"
35+
2336
- name: Get version from tag
2437
id: version
2538
shell: pwsh
2639
run: |
27-
$tag = $env:GITHUB_REF -replace 'refs/tags/',''
40+
$ErrorActionPreference = "Stop"
41+
if ($env:INPUT_VERSION) {
42+
$tag = $env:INPUT_VERSION
43+
} else {
44+
$tag = $env:GITHUB_REF -replace 'refs/tags/',''
45+
}
2846
if ($tag -notmatch '^v\d+\.\d+\.\d+$') {
29-
throw "Tag must be in format v1.2.3"
47+
throw "Version must be in format v1.2.3, got $tag"
3048
}
3149
$version = $tag -replace '^v',''
32-
$assemblyVersion = "$version.0"
33-
echo "VERSION=$version" >> $env:GITHUB_OUTPUT
34-
echo "ASSEMBLY_VERSION=$assemblyVersion" >> $env:GITHUB_OUTPUT
50+
$assemblyVersion = "$($version).0"
51+
Add-Content -Path $env:GITHUB_OUTPUT -Value "VERSION=$version"
52+
Add-Content -Path $env:GITHUB_OUTPUT -Value "ASSEMBLY_VERSION=$assemblyVersion"
53+
Write-Host "Version: $version"
54+
Write-Host "Assembly version: $assemblyVersion"
55+
env:
56+
INPUT_VERSION: ${{ inputs.version }}
3557

36-
- name: Build and publish x64
37-
run: |
38-
dotnet publish src/App/App.csproj -c Release -r win-x64 -p:Version=${{ steps.version.outputs.ASSEMBLY_VERSION }} -o publish/x64
39-
dotnet publish src/Vpn.Service/Vpn.Service.csproj -c Release -r win-x64 -p:Version=${{ steps.version.outputs.ASSEMBLY_VERSION }} -o publish/x64
58+
# Setup GCloud for signing Windows binaries.
59+
- name: Authenticate to Google Cloud
60+
id: gcloud_auth
61+
uses: google-github-actions/auth@71f986410dfbc7added4569d411d040a91dc6935 # v2.1.8
62+
with:
63+
workload_identity_provider: ${{ secrets.GCP_CODE_SIGNING_WORKLOAD_ID_PROVIDER }}
64+
service_account: ${{ secrets.GCP_CODE_SIGNING_SERVICE_ACCOUNT }}
65+
token_format: "access_token"
4066

41-
- name: Build and publish arm64
42-
run: |
43-
dotnet publish src/App/App.csproj -c Release -r win-arm64 -p:Version=${{ steps.version.outputs.ASSEMBLY_VERSION }} -o publish/arm64
44-
dotnet publish src/Vpn.Service/Vpn.Service.csproj -c Release -r win-arm64 -p:Version=${{ steps.version.outputs.ASSEMBLY_VERSION }} -o publish/arm64
67+
- name: Setup GCloud SDK
68+
uses: google-github-actions/setup-gcloud@77e7a554d41e2ee56fc945c52dfd3f33d12def9a # v2.1.4
4569

46-
- name: Create ZIP archives
70+
- name: scripts/Release.ps1
71+
id: release
4772
shell: pwsh
4873
run: |
49-
Compress-Archive -Path "publish/x64/*" -DestinationPath "./publish/CoderDesktop-${{ steps.version.outputs.VERSION }}-x64.zip"
50-
Compress-Archive -Path "publish/arm64/*" -DestinationPath "./publish/CoderDesktop-${{ steps.version.outputs.VERSION }}-arm64.zip"
74+
$ErrorActionPreference = "Stop"
5175
52-
- name: Create Release
53-
uses: softprops/action-gh-release@v1
76+
$env:EV_CERTIFICATE_PATH = Join-Path $env:TEMP "ev_cert.pem"
77+
$env:JSIGN_PATH = Join-Path $env:TEMP "jsign-6.0.jar"
78+
Invoke-WebRequest -Uri "https://github.com/ebourg/jsign/releases/download/6.0/jsign-6.0.jar" -OutFile $env:JSIGN_PATH
79+
80+
& ./scripts/Release.ps1 `
81+
-version ${{ steps.version.outputs.VERSION }} `
82+
-assemblyVersion ${{ steps.version.outputs.ASSEMBLY_VERSION }}
83+
if ($LASTEXITCODE -ne 0) { throw "Failed to publish" }
84+
env:
85+
EV_SIGNING_CERT: ${{ secrets.EV_SIGNING_CERT }}
86+
EV_KEYSTORE: ${{ secrets.EV_KEYSTORE }}
87+
EV_KEY: ${{ secrets.EV_KEY }}
88+
EV_TSA_URL: ${{ secrets.EV_TSA_URL }}
89+
GCLOUD_ACCESS_TOKEN: ${{ steps.gcloud_auth.outputs.access_token }}
90+
91+
- name: Upload artifact
92+
uses: actions/upload-artifact@v4
93+
with:
94+
name: publish
95+
path: .\publish\
96+
97+
- name: Create release
98+
uses: softprops/action-gh-release@v2
99+
if: startsWith(github.ref, 'refs/tags/')
54100
with:
55-
files: |
56-
./publish/CoderDesktop-${{ steps.version.outputs.VERSION }}-x64.zip
57-
./publish/CoderDesktop-${{ steps.version.outputs.VERSION }}-arm64.zip
58101
name: Release ${{ steps.version.outputs.VERSION }}
59102
generate_release_notes: true
103+
# We currently only release the bootstrappers, not the MSIs.
104+
files: |
105+
${{ steps.release.outputs.X64_OUTPUT_PATH }}
106+
${{ steps.release.outputs.ARM64_OUTPUT_PATH }}
60107
env:
61108
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

scripts/Publish.ps1

Lines changed: 76 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,68 @@ param (
1515
[string] $outputPath = "", # defaults to "publish\CoderDesktop-$version-$arch.exe"
1616

1717
[Parameter(Mandatory = $false)]
18-
[switch] $keepBuildTemp = $false
18+
[switch] $keepBuildTemp = $false,
19+
20+
[Parameter(Mandatory = $false)]
21+
[switch] $sign = $false
22+
)
23+
24+
$ErrorActionPreference = "Stop"
25+
26+
$ourAssemblies = @(
27+
"Coder Desktop.exe",
28+
"Coder Desktop.dll",
29+
"CoderVpnService.exe",
30+
"CoderVpnService.dll",
31+
32+
"Coder.Desktop.CoderSdk.dll",
33+
"Coder.Desktop.Vpn.dll",
34+
"Coder.Desktop.Vpn.Proto.dll"
1935
)
2036

37+
function Find-Dependencies([string[]] $dependencies) {
38+
foreach ($dependency in $dependencies) {
39+
if (!(Get-Command $dependency -ErrorAction SilentlyContinue)) {
40+
throw "Missing dependency: $dependency"
41+
}
42+
}
43+
}
44+
45+
function Find-EnvironmentVariables([string[]] $variables) {
46+
foreach ($variable in $variables) {
47+
if (!(Get-Item env:$variable -ErrorAction SilentlyContinue)) {
48+
throw "Missing environment variable: $variable"
49+
}
50+
}
51+
}
52+
53+
if ($sign) {
54+
Write-Host "Signing is enabled"
55+
Find-Dependencies java
56+
Find-EnvironmentVariables @("JSIGN_PATH", "EV_KEYSTORE", "EV_KEY", "EV_CERTIFICATE_PATH", "EV_TSA_URL", "GCLOUD_ACCESS_TOKEN")
57+
}
58+
59+
function Add-CoderSignature([string] $path) {
60+
if (!$sign) {
61+
Write-Host "Skipping signing $path"
62+
return
63+
}
64+
65+
Write-Host "Signing $path"
66+
& java.exe -jar $env:JSIGN_PATH `
67+
--storetype GOOGLECLOUD `
68+
--storepass $env:GCLOUD_ACCESS_TOKEN `
69+
--keystore $env:EV_KEYSTORE `
70+
--alias $env:EV_KEY `
71+
--certfile $env:EV_CERTIFICATE_PATH `
72+
--tsmode RFC3161 `
73+
--tsaurl $env:EV_TSA_URL `
74+
$path
75+
if ($LASTEXITCODE -ne 0) { throw "Failed to sign $path" }
76+
}
77+
2178
# CD to the root of the repo
22-
$repoRoot = Join-Path $PSScriptRoot ".."
79+
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
2380
Push-Location $repoRoot
2481

2582
if ($msiOutputPath -eq "") {
@@ -48,11 +105,21 @@ New-Item -ItemType Directory -Path $buildPath -Force
48105

49106
# Build in release mode
50107
$servicePublishDir = Join-Path $buildPath "service"
51-
dotnet.exe publish .\Vpn.Service\Vpn.Service.csproj -c Release -a $arch -o $servicePublishDir
108+
& dotnet.exe publish .\Vpn.Service\Vpn.Service.csproj -c Release -a $arch -o $servicePublishDir
109+
if ($LASTEXITCODE -ne 0) { throw "Failed to build Vpn.Service" }
52110
# App needs to be built with msbuild
53111
$appPublishDir = Join-Path $buildPath "app"
54112
$msbuildBinary = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe
113+
if ($LASTEXITCODE -ne 0) { throw "Failed to find MSBuild" }
55114
& $msbuildBinary .\App\App.csproj /p:Configuration=Release /p:Platform=$arch /p:OutputPath=$appPublishDir
115+
if ($LASTEXITCODE -ne 0) { throw "Failed to build App" }
116+
117+
# Find any files in the publish directory recursively that match any of our
118+
# assemblies and sign them.
119+
$toSign = Get-ChildItem -Path $buildPath -Recurse | Where-Object { $ourAssemblies -contains $_.Name }
120+
foreach ($file in $toSign) {
121+
Add-CoderSignature $file.FullName
122+
}
56123

57124
# Copy any additional files into the install directory
58125
Copy-Item "scripts\files\License.txt" $buildPath
@@ -63,7 +130,7 @@ $wintunDllPath = Join-Path $vpnFilesPath "wintun.dll"
63130
Copy-Item "scripts\files\wintun-*-$($arch).dll" $wintunDllPath
64131

65132
# Build the MSI installer
66-
dotnet.exe run --project .\Installer\Installer.csproj -c Release -- `
133+
& dotnet.exe run --project .\Installer\Installer.csproj -c Release -- `
67134
build-msi `
68135
--arch $arch `
69136
--version $version `
@@ -77,11 +144,11 @@ dotnet.exe run --project .\Installer\Installer.csproj -c Release -- `
77144
--vpn-dir "vpn" `
78145
--banner-bmp "scripts\files\WixUIBannerBmp.bmp" `
79146
--dialog-bmp "scripts\files\WixUIDialogBmp.bmp"
80-
81-
# TODO: sign the installer
147+
if ($LASTEXITCODE -ne 0) { throw "Failed to build MSI" }
148+
Add-CoderSignature $msiOutputPath
82149

83150
# Build the bootstrapper
84-
dotnet.exe run --project .\Installer\Installer.csproj -c Release -- `
151+
& dotnet.exe run --project .\Installer\Installer.csproj -c Release -- `
85152
build-bootstrapper `
86153
--arch $arch `
87154
--version $version `
@@ -90,8 +157,8 @@ dotnet.exe run --project .\Installer\Installer.csproj -c Release -- `
90157
--icon-file "App\coder.ico" `
91158
--msi-path $msiOutputPath `
92159
--logo-png "scripts\files\logo.png"
93-
94-
# TODO: sign the bootstrapper
160+
if ($LASTEXITCODE -ne 0) { throw "Failed to build bootstrapper" }
161+
Add-CoderSignature $outputPath
95162

96163
if (!$keepBuildTemp) {
97164
Remove-Item -Recurse -Force $buildPath

scripts/Release.ps1

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Usage: Release.ps1 -version <version>
2+
param (
3+
[Parameter(Mandatory = $true)]
4+
[ValidatePattern("^\d+\.\d+\.\d+\.\d+$")]
5+
[string] $version,
6+
7+
[Parameter(Mandatory = $true)]
8+
[ValidatePattern("^\d+\.\d+\.\d+\.\d+$")]
9+
[string] $assemblyVersion
10+
)
11+
12+
$ErrorActionPreference = "Stop"
13+
14+
foreach ($arch in @("x64", "arm64")) {
15+
Write-Host "::group::Publishing $arch"
16+
try {
17+
$archUpper = $arch.ToUpper()
18+
19+
$msiOutputPath = "publish/CoderDesktopCore-$version-$arch.msi"
20+
Add-Content -Path $env:GITHUB_OUTPUT -Value "$($archUpper)_MSI_OUTPUT_PATH=$msiOutputPath"
21+
Write-Host "MSI_OUTPUT_PATH=$msiOutputPath"
22+
23+
$outputPath = "publish/CoderDesktop-$version-$arch.exe"
24+
Add-Content -Path $env:GITHUB_OUTPUT -Value "$($archUpper)_OUTPUT_PATH=$outputPath"
25+
Write-Host "OUTPUT_PATH=$outputPath"
26+
27+
$publishScript = Join-Path $PSScriptRoot "Publish.ps1"
28+
& $publishScript `
29+
-version $assemblyVersion `
30+
-arch $arch `
31+
-msiOutputPath $msiOutputPath `
32+
-outputPath $outputPath `
33+
-sign
34+
if ($LASTEXITCODE -ne 0) { throw "Failed to publish" }
35+
36+
# Verify that the output exe is authenticode signed
37+
$sig = Get-AuthenticodeSignature $outputPath
38+
if ($sig.Status -ne "Valid") {
39+
throw "Output file is not authenticode signed"
40+
}
41+
else {
42+
Write-Host "Output file is authenticode signed"
43+
}
44+
}
45+
finally {
46+
Write-Host "::endgroup::"
47+
}
48+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy