From bb7bc875c351bc0dfcc410b22fd6ec156fc86965 Mon Sep 17 00:00:00 2001 From: Dean Sheather Date: Fri, 28 Feb 2025 15:08:42 +1100 Subject: [PATCH] chore: installer CI --- .github/workflows/release.yaml | 89 ++++++++++++++++++++++++++-------- scripts/Publish.ps1 | 85 ++++++++++++++++++++++++++++---- scripts/Release.ps1 | 48 ++++++++++++++++++ 3 files changed, 192 insertions(+), 30 deletions(-) create mode 100644 scripts/Release.ps1 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index b9810fd..cb7df6e 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -4,6 +4,12 @@ on: push: tags: - '*' + workflow_dispatch: + inputs: + version: + description: 'Version number (e.g. 1.2.3)' + required: true + default: '1.2.3' permissions: contents: write @@ -20,42 +26,83 @@ jobs: with: dotnet-version: '8.0.x' + # Necessary for signing Windows binaries. + - name: Setup Java + uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 + with: + distribution: "zulu" + java-version: "11.0" + - name: Get version from tag id: version shell: pwsh run: | - $tag = $env:GITHUB_REF -replace 'refs/tags/','' + $ErrorActionPreference = "Stop" + if ($env:INPUT_VERSION) { + $tag = $env:INPUT_VERSION + } else { + $tag = $env:GITHUB_REF -replace 'refs/tags/','' + } if ($tag -notmatch '^v\d+\.\d+\.\d+$') { - throw "Tag must be in format v1.2.3" + throw "Version must be in format v1.2.3, got $tag" } $version = $tag -replace '^v','' - $assemblyVersion = "$version.0" - echo "VERSION=$version" >> $env:GITHUB_OUTPUT - echo "ASSEMBLY_VERSION=$assemblyVersion" >> $env:GITHUB_OUTPUT + $assemblyVersion = "$($version).0" + Add-Content -Path $env:GITHUB_OUTPUT -Value "VERSION=$version" + Add-Content -Path $env:GITHUB_OUTPUT -Value "ASSEMBLY_VERSION=$assemblyVersion" + Write-Host "Version: $version" + Write-Host "Assembly version: $assemblyVersion" + env: + INPUT_VERSION: ${{ inputs.version }} - - name: Build and publish x64 - run: | - dotnet publish src/App/App.csproj -c Release -r win-x64 -p:Version=${{ steps.version.outputs.ASSEMBLY_VERSION }} -o publish/x64 - dotnet publish src/Vpn.Service/Vpn.Service.csproj -c Release -r win-x64 -p:Version=${{ steps.version.outputs.ASSEMBLY_VERSION }} -o publish/x64 + # Setup GCloud for signing Windows binaries. + - name: Authenticate to Google Cloud + id: gcloud_auth + uses: google-github-actions/auth@71f986410dfbc7added4569d411d040a91dc6935 # v2.1.8 + with: + workload_identity_provider: ${{ secrets.GCP_CODE_SIGNING_WORKLOAD_ID_PROVIDER }} + service_account: ${{ secrets.GCP_CODE_SIGNING_SERVICE_ACCOUNT }} + token_format: "access_token" - - name: Build and publish arm64 - run: | - dotnet publish src/App/App.csproj -c Release -r win-arm64 -p:Version=${{ steps.version.outputs.ASSEMBLY_VERSION }} -o publish/arm64 - dotnet publish src/Vpn.Service/Vpn.Service.csproj -c Release -r win-arm64 -p:Version=${{ steps.version.outputs.ASSEMBLY_VERSION }} -o publish/arm64 + - name: Setup GCloud SDK + uses: google-github-actions/setup-gcloud@77e7a554d41e2ee56fc945c52dfd3f33d12def9a # v2.1.4 - - name: Create ZIP archives + - name: scripts/Release.ps1 + id: release shell: pwsh run: | - Compress-Archive -Path "publish/x64/*" -DestinationPath "./publish/CoderDesktop-${{ steps.version.outputs.VERSION }}-x64.zip" - Compress-Archive -Path "publish/arm64/*" -DestinationPath "./publish/CoderDesktop-${{ steps.version.outputs.VERSION }}-arm64.zip" + $ErrorActionPreference = "Stop" - - name: Create Release - uses: softprops/action-gh-release@v1 + $env:EV_CERTIFICATE_PATH = Join-Path $env:TEMP "ev_cert.pem" + $env:JSIGN_PATH = Join-Path $env:TEMP "jsign-6.0.jar" + Invoke-WebRequest -Uri "https://github.com/ebourg/jsign/releases/download/6.0/jsign-6.0.jar" -OutFile $env:JSIGN_PATH + + & ./scripts/Release.ps1 ` + -version ${{ steps.version.outputs.VERSION }} ` + -assemblyVersion ${{ steps.version.outputs.ASSEMBLY_VERSION }} + if ($LASTEXITCODE -ne 0) { throw "Failed to publish" } + env: + EV_SIGNING_CERT: ${{ secrets.EV_SIGNING_CERT }} + EV_KEYSTORE: ${{ secrets.EV_KEYSTORE }} + EV_KEY: ${{ secrets.EV_KEY }} + EV_TSA_URL: ${{ secrets.EV_TSA_URL }} + GCLOUD_ACCESS_TOKEN: ${{ steps.gcloud_auth.outputs.access_token }} + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: publish + path: .\publish\ + + - name: Create release + uses: softprops/action-gh-release@v2 + if: startsWith(github.ref, 'refs/tags/') with: - files: | - ./publish/CoderDesktop-${{ steps.version.outputs.VERSION }}-x64.zip - ./publish/CoderDesktop-${{ steps.version.outputs.VERSION }}-arm64.zip name: Release ${{ steps.version.outputs.VERSION }} generate_release_notes: true + # We currently only release the bootstrappers, not the MSIs. + files: | + ${{ steps.release.outputs.X64_OUTPUT_PATH }} + ${{ steps.release.outputs.ARM64_OUTPUT_PATH }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/scripts/Publish.ps1 b/scripts/Publish.ps1 index 55a2144..2bdc7d6 100644 --- a/scripts/Publish.ps1 +++ b/scripts/Publish.ps1 @@ -15,11 +15,68 @@ param ( [string] $outputPath = "", # defaults to "publish\CoderDesktop-$version-$arch.exe" [Parameter(Mandatory = $false)] - [switch] $keepBuildTemp = $false + [switch] $keepBuildTemp = $false, + + [Parameter(Mandatory = $false)] + [switch] $sign = $false +) + +$ErrorActionPreference = "Stop" + +$ourAssemblies = @( + "Coder Desktop.exe", + "Coder Desktop.dll", + "CoderVpnService.exe", + "CoderVpnService.dll", + + "Coder.Desktop.CoderSdk.dll", + "Coder.Desktop.Vpn.dll", + "Coder.Desktop.Vpn.Proto.dll" ) +function Find-Dependencies([string[]] $dependencies) { + foreach ($dependency in $dependencies) { + if (!(Get-Command $dependency -ErrorAction SilentlyContinue)) { + throw "Missing dependency: $dependency" + } + } +} + +function Find-EnvironmentVariables([string[]] $variables) { + foreach ($variable in $variables) { + if (!(Get-Item env:$variable -ErrorAction SilentlyContinue)) { + throw "Missing environment variable: $variable" + } + } +} + +if ($sign) { + Write-Host "Signing is enabled" + Find-Dependencies java + Find-EnvironmentVariables @("JSIGN_PATH", "EV_KEYSTORE", "EV_KEY", "EV_CERTIFICATE_PATH", "EV_TSA_URL", "GCLOUD_ACCESS_TOKEN") +} + +function Add-CoderSignature([string] $path) { + if (!$sign) { + Write-Host "Skipping signing $path" + return + } + + Write-Host "Signing $path" + & java.exe -jar $env:JSIGN_PATH ` + --storetype GOOGLECLOUD ` + --storepass $env:GCLOUD_ACCESS_TOKEN ` + --keystore $env:EV_KEYSTORE ` + --alias $env:EV_KEY ` + --certfile $env:EV_CERTIFICATE_PATH ` + --tsmode RFC3161 ` + --tsaurl $env:EV_TSA_URL ` + $path + if ($LASTEXITCODE -ne 0) { throw "Failed to sign $path" } +} + # CD to the root of the repo -$repoRoot = Join-Path $PSScriptRoot ".." +$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..") Push-Location $repoRoot if ($msiOutputPath -eq "") { @@ -48,11 +105,21 @@ New-Item -ItemType Directory -Path $buildPath -Force # Build in release mode $servicePublishDir = Join-Path $buildPath "service" -dotnet.exe publish .\Vpn.Service\Vpn.Service.csproj -c Release -a $arch -o $servicePublishDir +& dotnet.exe publish .\Vpn.Service\Vpn.Service.csproj -c Release -a $arch -o $servicePublishDir +if ($LASTEXITCODE -ne 0) { throw "Failed to build Vpn.Service" } # App needs to be built with msbuild $appPublishDir = Join-Path $buildPath "app" $msbuildBinary = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe +if ($LASTEXITCODE -ne 0) { throw "Failed to find MSBuild" } & $msbuildBinary .\App\App.csproj /p:Configuration=Release /p:Platform=$arch /p:OutputPath=$appPublishDir +if ($LASTEXITCODE -ne 0) { throw "Failed to build App" } + +# Find any files in the publish directory recursively that match any of our +# assemblies and sign them. +$toSign = Get-ChildItem -Path $buildPath -Recurse | Where-Object { $ourAssemblies -contains $_.Name } +foreach ($file in $toSign) { + Add-CoderSignature $file.FullName +} # Copy any additional files into the install directory Copy-Item "scripts\files\License.txt" $buildPath @@ -63,7 +130,7 @@ $wintunDllPath = Join-Path $vpnFilesPath "wintun.dll" Copy-Item "scripts\files\wintun-*-$($arch).dll" $wintunDllPath # Build the MSI installer -dotnet.exe run --project .\Installer\Installer.csproj -c Release -- ` +& dotnet.exe run --project .\Installer\Installer.csproj -c Release -- ` build-msi ` --arch $arch ` --version $version ` @@ -77,11 +144,11 @@ dotnet.exe run --project .\Installer\Installer.csproj -c Release -- ` --vpn-dir "vpn" ` --banner-bmp "scripts\files\WixUIBannerBmp.bmp" ` --dialog-bmp "scripts\files\WixUIDialogBmp.bmp" - -# TODO: sign the installer +if ($LASTEXITCODE -ne 0) { throw "Failed to build MSI" } +Add-CoderSignature $msiOutputPath # Build the bootstrapper -dotnet.exe run --project .\Installer\Installer.csproj -c Release -- ` +& dotnet.exe run --project .\Installer\Installer.csproj -c Release -- ` build-bootstrapper ` --arch $arch ` --version $version ` @@ -90,8 +157,8 @@ dotnet.exe run --project .\Installer\Installer.csproj -c Release -- ` --icon-file "App\coder.ico" ` --msi-path $msiOutputPath ` --logo-png "scripts\files\logo.png" - -# TODO: sign the bootstrapper +if ($LASTEXITCODE -ne 0) { throw "Failed to build bootstrapper" } +Add-CoderSignature $outputPath if (!$keepBuildTemp) { Remove-Item -Recurse -Force $buildPath diff --git a/scripts/Release.ps1 b/scripts/Release.ps1 new file mode 100644 index 0000000..40bb0fa --- /dev/null +++ b/scripts/Release.ps1 @@ -0,0 +1,48 @@ +# Usage: Release.ps1 -version +param ( + [Parameter(Mandatory = $true)] + [ValidatePattern("^\d+\.\d+\.\d+\.\d+$")] + [string] $version, + + [Parameter(Mandatory = $true)] + [ValidatePattern("^\d+\.\d+\.\d+\.\d+$")] + [string] $assemblyVersion +) + +$ErrorActionPreference = "Stop" + +foreach ($arch in @("x64", "arm64")) { + Write-Host "::group::Publishing $arch" + try { + $archUpper = $arch.ToUpper() + + $msiOutputPath = "publish/CoderDesktopCore-$version-$arch.msi" + Add-Content -Path $env:GITHUB_OUTPUT -Value "$($archUpper)_MSI_OUTPUT_PATH=$msiOutputPath" + Write-Host "MSI_OUTPUT_PATH=$msiOutputPath" + + $outputPath = "publish/CoderDesktop-$version-$arch.exe" + Add-Content -Path $env:GITHUB_OUTPUT -Value "$($archUpper)_OUTPUT_PATH=$outputPath" + Write-Host "OUTPUT_PATH=$outputPath" + + $publishScript = Join-Path $PSScriptRoot "Publish.ps1" + & $publishScript ` + -version $assemblyVersion ` + -arch $arch ` + -msiOutputPath $msiOutputPath ` + -outputPath $outputPath ` + -sign + if ($LASTEXITCODE -ne 0) { throw "Failed to publish" } + + # Verify that the output exe is authenticode signed + $sig = Get-AuthenticodeSignature $outputPath + if ($sig.Status -ne "Valid") { + throw "Output file is not authenticode signed" + } + else { + Write-Host "Output file is authenticode signed" + } + } + finally { + Write-Host "::endgroup::" + } +} 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