Repository navigation
ci: skip Control package publish on dispatch (#23) #139
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build Terminal | |
| on: | |
| push: | |
| branches: | |
| - "**" | |
| tags: | |
| - "v*" | |
| paths-ignore: | |
| - ".github/workflows/build-ghostty.yml" | |
| pull_request: | |
| paths-ignore: | |
| - ".github/workflows/build-ghostty.yml" | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: Release version to build/publish (X.Y.Z, for example 2026.3.0). | |
| required: true | |
| type: string | |
| dry_run: | |
| description: Dry run. | |
| required: false | |
| default: false | |
| type: boolean | |
| sign_dry_run: | |
| description: Sign Windows packages during a dry run when signing credentials are available. | |
| required: false | |
| default: false | |
| type: boolean | |
| github-env: | |
| description: GitHub Environment for code-signing secrets. | |
| required: false | |
| default: auto | |
| type: choice | |
| options: | |
| - auto | |
| - test | |
| - prod | |
| permissions: | |
| contents: read | |
| env: | |
| NUGET_PACKAGE_SOURCE: https://api.nuget.org/v3/index.json | |
| jobs: | |
| release-metadata: | |
| name: Resolve release metadata | |
| runs-on: ubuntu-latest | |
| outputs: | |
| release_tag: ${{ steps.resolve.outputs.release_tag }} | |
| release_version: ${{ steps.resolve.outputs.release_version }} | |
| msix_version: ${{ steps.resolve.outputs.msix_version }} | |
| dry_run: ${{ steps.resolve.outputs.dry_run }} | |
| sign_dry_run: ${{ steps.resolve.outputs.sign_dry_run }} | |
| publish_environment: ${{ steps.resolve.outputs.publish_environment }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Resolve tag, versions, and signing environment | |
| id: resolve | |
| shell: pwsh | |
| env: | |
| REQUESTED_VERSION: ${{ inputs.version }} | |
| DRY_RUN: ${{ inputs.dry_run }} | |
| SIGN_DRY_RUN: ${{ inputs.sign_dry_run }} | |
| REQUESTED_ENVIRONMENT: ${{ inputs['github-env'] }} | |
| run: | | |
| $dryRun = if ([string]::IsNullOrWhiteSpace($env:DRY_RUN)) { "false" } else { $env:DRY_RUN.ToLowerInvariant() } | |
| if ($dryRun -notin @("true", "false")) { | |
| throw "dry_run must be true or false." | |
| } | |
| $signDryRun = if ([string]::IsNullOrWhiteSpace($env:SIGN_DRY_RUN)) { "false" } else { $env:SIGN_DRY_RUN.ToLowerInvariant() } | |
| if ($signDryRun -notin @("true", "false")) { | |
| throw "sign_dry_run must be true or false." | |
| } | |
| if ($dryRun -eq "true" -and $signDryRun -ne "true") { | |
| $publishEnvironment = "publish-dry-run" | |
| } | |
| else { | |
| $requestedEnvironment = if ([string]::IsNullOrWhiteSpace($env:REQUESTED_ENVIRONMENT)) { "auto" } else { $env:REQUESTED_ENVIRONMENT.ToLowerInvariant() } | |
| $publishEnvironment = switch ($requestedEnvironment) { | |
| "auto" { if ($env:GITHUB_REF_TYPE -eq "tag" -or $env:GITHUB_REF_NAME -eq "master") { "publish-prod" } else { "publish-test" } } | |
| "test" { "publish-test" } | |
| "prod" { "publish-prod" } | |
| default { throw "Unsupported github-env value: $requestedEnvironment. Expected auto, test, or prod." } | |
| } | |
| } | |
| $requestedVersion = if ($null -eq $env:REQUESTED_VERSION) { "" } else { $env:REQUESTED_VERSION.Trim() } | |
| $tag = $null | |
| if (-not [string]::IsNullOrWhiteSpace($requestedVersion)) { | |
| if ($requestedVersion -notmatch '^(\d+)\.(\d+)\.(\d+)$') { | |
| throw "Release version must use X.Y.Z; received '$($env:REQUESTED_VERSION)'." | |
| } | |
| $releaseVersion = $requestedVersion | |
| $tag = "v$releaseVersion" | |
| } | |
| elseif ($env:GITHUB_REF_TYPE -eq "tag") { | |
| $tag = $env:GITHUB_REF_NAME | |
| if ($tag -notmatch '^v(\d+)\.(\d+)\.(\d+)$') { | |
| throw "Release tag must be vYYYY.MAJOR.PATCH; received '$tag'." | |
| } | |
| $releaseVersion = "$($Matches[1]).$($Matches[2]).$($Matches[3])" | |
| } | |
| else { | |
| [xml]$props = Get-Content -LiteralPath "Directory.Build.props" | |
| $releaseVersion = $props.Project.PropertyGroup.VersionPrefix | |
| if ($releaseVersion -notmatch '^\d+\.\d+\.\d+$') { | |
| throw "Directory.Build.props must contain a three-component numeric VersionPrefix." | |
| } | |
| $tag = "v$releaseVersion.$env:GITHUB_RUN_NUMBER" | |
| } | |
| $msixVersion = "$releaseVersion.0" | |
| @( | |
| "release_tag=$tag" | |
| "release_version=$releaseVersion" | |
| "msix_version=$msixVersion" | |
| "dry_run=$dryRun" | |
| "sign_dry_run=$signDryRun" | |
| "publish_environment=$publishEnvironment" | |
| ) | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append | |
| build: | |
| name: Build and test | |
| runs-on: windows-latest | |
| needs: release-metadata | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Cache native libraries | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| artifacts/tools | |
| artifacts/ghostty-src | |
| native/ghostty/*/* | |
| native/linux-pty/*/dt-pty-host | |
| native/noto-emoji/NotoColorEmoji.ttf | |
| # Never restore native sources or reuse binaries built from different inputs. | |
| key: native-v2-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('native/**/*.ps1', 'native/ghostty/ghostty-upstream.json', 'native/linux-pty/dt-pty-host.c', 'native/noto-emoji/noto-emoji.json') }} | |
| - name: Set up .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: 10.0.x | |
| - name: Restore | |
| run: dotnet restore Devolutions.Terminal.slnx -p:VersionPrefix=${{ needs.release-metadata.outputs.release_version }} | |
| - name: Build | |
| run: dotnet build Devolutions.Terminal.slnx -c Release --no-restore -p:VersionPrefix=${{ needs.release-metadata.outputs.release_version }} | |
| - name: Test | |
| run: dotnet test Devolutions.Terminal.slnx -c Release --no-build -p:VersionPrefix=${{ needs.release-metadata.outputs.release_version }} | |
| nuget-pack: | |
| name: Pack Devolutions.Terminal.Control NuGet package | |
| runs-on: windows-latest | |
| needs: | |
| - release-metadata | |
| - build | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Cache native libraries | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| native/noto-emoji/NotoColorEmoji.ttf | |
| key: native-v2-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('native/**/*.ps1', 'native/ghostty/ghostty-upstream.json', 'native/linux-pty/dt-pty-host.c', 'native/noto-emoji/noto-emoji.json') }} | |
| - name: Set up .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: 10.0.x | |
| - name: Pack Devolutions.Terminal.Control | |
| run: >- | |
| dotnet pack src/Devolutions.Terminal.Control/Devolutions.Terminal.Control.csproj | |
| -c Release | |
| -o artifacts/nuget | |
| -p:VersionPrefix=${{ needs.release-metadata.outputs.release_version }} | |
| - name: Smoke-test package via samples/Devolutions.Terminal.Control.Sample | |
| run: dotnet build samples/Devolutions.Terminal.Control.Sample -c Release | |
| - name: Upload NuGet package artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-nuget-packages | |
| path: artifacts/nuget | |
| if-no-files-found: error | |
| nuget-publish: | |
| name: Publish Devolutions.Terminal.Control to nuget.org | |
| if: ${{ needs.release-metadata.outputs.dry_run != 'true' && startsWith(github.ref, 'refs/tags/') }} | |
| needs: | |
| - release-metadata | |
| - nuget-pack | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: nuget.org | |
| steps: | |
| - name: Set up .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: 10.0.x | |
| - name: Download NuGet package artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-nuget-packages | |
| path: artifacts/nuget | |
| - name: Push to nuget.org | |
| env: | |
| NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} | |
| shell: pwsh | |
| run: | | |
| if ([string]::IsNullOrWhiteSpace($env:NUGET_API_KEY)) { | |
| throw "Missing NUGET_API_KEY secret." | |
| } | |
| $packages = Get-ChildItem -LiteralPath artifacts/nuget -Filter "*.nupkg" -File | |
| if ($packages.Count -eq 0) { | |
| throw "No .nupkg files found to publish." | |
| } | |
| foreach ($package in $packages) { | |
| dotnet nuget push $package.FullName ` | |
| --source https://api.nuget.org/v3/index.json ` | |
| --api-key $env:NUGET_API_KEY ` | |
| --skip-duplicate | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "Failed to push $($package.Name) to nuget.org." | |
| } | |
| } | |
| native-aot: | |
| name: NativeAOT ${{ matrix.rid }} | |
| runs-on: windows-latest | |
| needs: release-metadata | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| rid: | |
| - win-x64 | |
| - win-arm64 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Cache native libraries | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| artifacts/tools | |
| artifacts/ghostty-src | |
| native/ghostty/*/* | |
| native/linux-pty/*/dt-pty-host | |
| native/noto-emoji/NotoColorEmoji.ttf | |
| key: native-v2-${{ runner.os }}-${{ runner.arch }}-${{ matrix.rid }}-${{ hashFiles('native/**/*.ps1', 'native/ghostty/ghostty-upstream.json', 'native/linux-pty/dt-pty-host.c', 'native/noto-emoji/noto-emoji.json') }} | |
| - name: Set up .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: 10.0.x | |
| - name: Publish | |
| run: > | |
| dotnet publish src/Devolutions.Terminal/Devolutions.Terminal.csproj | |
| -c Release | |
| -r ${{ matrix.rid }} | |
| --self-contained | |
| -p:VersionPrefix=${{ needs.release-metadata.outputs.release_version }} | |
| -o artifacts/${{ matrix.rid }} | |
| - name: Upload native executable | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-${{ matrix.rid }} | |
| path: artifacts/${{ matrix.rid }} | |
| if-no-files-found: error | |
| linux-managed: | |
| name: Linux managed and metadata tests | |
| runs-on: ubuntu-24.04 | |
| needs: release-metadata | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Cache native libraries | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| artifacts/tools | |
| artifacts/ghostty-src | |
| native/ghostty/*/* | |
| native/linux-pty/*/dt-pty-host | |
| native/noto-emoji/NotoColorEmoji.ttf | |
| key: native-v2-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('native/**/*.ps1', 'native/ghostty/ghostty-upstream.json', 'native/linux-pty/dt-pty-host.c', 'native/noto-emoji/noto-emoji.json') }} | |
| - name: Set up .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: 10.0.x | |
| - name: Restore | |
| run: dotnet restore Devolutions.Terminal.slnx -p:VersionPrefix=${{ needs.release-metadata.outputs.release_version }} | |
| - name: Run managed tests | |
| run: dotnet test Devolutions.Terminal.slnx -c Release --no-restore -p:VersionPrefix=${{ needs.release-metadata.outputs.release_version }} | |
| - name: Validate Linux scripts and metadata | |
| run: bash scripts/Test-LinuxPackagingMetadata.sh | |
| macos-managed: | |
| name: macOS managed and metadata tests | |
| runs-on: macos-14 | |
| needs: release-metadata | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Cache native libraries | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| artifacts/tools | |
| artifacts/ghostty-src | |
| native/ghostty/*/* | |
| native/linux-pty/*/dt-pty-host | |
| native/noto-emoji/NotoColorEmoji.ttf | |
| key: native-v2-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('native/**/*.ps1', 'native/ghostty/ghostty-upstream.json', 'native/linux-pty/dt-pty-host.c', 'native/noto-emoji/noto-emoji.json') }} | |
| - name: Set up .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: 10.0.x | |
| - name: Restore | |
| run: dotnet restore Devolutions.Terminal.slnx -p:VersionPrefix=${{ needs.release-metadata.outputs.release_version }} | |
| - name: Run managed tests | |
| run: dotnet test Devolutions.Terminal.slnx -c Release --no-restore -p:VersionPrefix=${{ needs.release-metadata.outputs.release_version }} | |
| - name: Validate macOS scripts and metadata | |
| run: pwsh -NoLogo -NoProfile -File scripts/Test-MacOsPackagingMetadata.ps1 | |
| macos-native-aot: | |
| name: macOS NativeAOT ${{ matrix.rid }} | |
| runs-on: macos-14 | |
| needs: | |
| - macos-managed | |
| - release-metadata | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| rid: | |
| - osx-arm64 | |
| - osx-x64 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Cache native libraries | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| artifacts/tools | |
| artifacts/ghostty-src | |
| native/ghostty/*/* | |
| native/linux-pty/*/dt-pty-host | |
| native/noto-emoji/NotoColorEmoji.ttf | |
| key: native-v2-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('native/**/*.ps1', 'native/ghostty/ghostty-upstream.json', 'native/linux-pty/dt-pty-host.c', 'native/noto-emoji/noto-emoji.json') }} | |
| - name: Set up .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: 10.0.x | |
| - name: NativeAOT publish | |
| run: > | |
| dotnet publish src/Devolutions.Terminal/Devolutions.Terminal.csproj | |
| -c Release | |
| -r ${{ matrix.rid }} | |
| --self-contained true | |
| -p:VersionPrefix=${{ needs.release-metadata.outputs.release_version }} | |
| -p:DebugSymbols=false | |
| -p:DebugType=None | |
| -p:NativeDebugSymbols=false | |
| -o artifacts/publish/${{ matrix.rid }} | |
| - name: Upload macOS NativeAOT publish | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-${{ matrix.rid }} | |
| path: artifacts/publish/${{ matrix.rid }} | |
| if-no-files-found: error | |
| - name: Build app bundle and zip | |
| env: | |
| MACOS_PUBLISH_DIR: ${{ github.workspace }}/artifacts/publish/${{ matrix.rid }} | |
| SOURCE_DATE_EPOCH: "1704067200" | |
| run: > | |
| pwsh -NoLogo -NoProfile -File scripts/Build-MacOsPackage.ps1 ${{ matrix.rid }} | |
| "${{ needs.release-metadata.outputs.release_version }}" | |
| artifacts/macos-packages | |
| - name: Validate package without launching UI | |
| run: pwsh -NoLogo -NoProfile -File scripts/Test-MacOsPackage.ps1 ${{ matrix.rid }} artifacts/macos-packages/*.zip | |
| - name: Run native non-UI gates | |
| # NativeAOT osx-x64 binaries are cross-compiled on the arm64 runner and | |
| # cannot be executed here (no Rosetta on Actions macOS images); only the | |
| # host's own architecture gets the runtime smoke/unit test gates. | |
| if: matrix.rid == 'osx-arm64' | |
| run: pwsh -NoLogo -NoProfile -File scripts/Test-MacOsRuntime.ps1 artifacts/macos-packages | |
| - name: Upload macOS package artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-${{ matrix.rid }}-packages | |
| path: artifacts/macos-packages | |
| if-no-files-found: error | |
| macos-sign: | |
| name: macOS sign and notarize ${{ matrix.rid }} | |
| runs-on: macos-14 | |
| if: ${{ github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/') }} | |
| needs: | |
| - macos-native-aot | |
| - release-metadata | |
| environment: | |
| # Use the same release environment as the downstream publish jobs so macOS signing | |
| # secrets are available for workflow_dispatch and tag releases. | |
| name: ${{ (github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/')) && needs.release-metadata.outputs.publish_environment || 'publish-dry-run' }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| rid: | |
| - osx-arm64 | |
| - osx-x64 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Download unsigned macOS package | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-${{ matrix.rid }}-packages | |
| path: artifacts/macos-packages | |
| - name: Resolve signing mode | |
| id: signing-mode | |
| shell: pwsh | |
| env: | |
| APPLE_APP_DEV_ID_APP_CERTIFICATE: ${{ secrets.APPLE_APP_DEV_ID_APP_CERTIFICATE }} | |
| APPLE_APP_DEV_ID_APP_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_APP_DEV_ID_APP_CERTIFICATE_PASSWORD }} | |
| APPLE_BOT_PASSWORD: ${{ secrets.APPLE_BOT_PASSWORD }} | |
| run: | | |
| $dryRun = '${{ needs.release-metadata.outputs.dry_run }}' | |
| $signDryRun = '${{ needs.release-metadata.outputs.sign_dry_run }}' | |
| $required = @( | |
| 'APPLE_APP_DEV_ID_APP_CERTIFICATE', | |
| 'APPLE_APP_DEV_ID_APP_CERTIFICATE_PASSWORD', | |
| 'APPLE_BOT_PASSWORD' | |
| ) | |
| $missing = @($required | Where-Object { [string]::IsNullOrEmpty([System.Environment]::GetEnvironmentVariable($_)) }) | |
| if ($dryRun -eq 'true' -and $signDryRun -ne 'true') { | |
| Add-Content -LiteralPath $env:GITHUB_OUTPUT -Value 'should_sign=false' | |
| Write-Host 'Dry run will not sign or notarize macOS packages.' | |
| exit 0 | |
| } | |
| if ($missing.Count -gt 0) { | |
| Add-Content -LiteralPath $env:GITHUB_OUTPUT -Value 'should_sign=false' | |
| Write-Host "::notice::Skipping macOS signing and notarization because these secrets are unavailable: $($missing -join ', ')" | |
| exit 0 | |
| } | |
| Add-Content -LiteralPath $env:GITHUB_OUTPUT -Value 'should_sign=true' | |
| - name: Import Developer ID certificate | |
| if: steps.signing-mode.outputs.should_sign == 'true' | |
| shell: pwsh | |
| env: | |
| APPLE_APP_DEV_ID_APP_CERTIFICATE: ${{ secrets.APPLE_APP_DEV_ID_APP_CERTIFICATE }} | |
| APPLE_APP_DEV_ID_APP_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_APP_DEV_ID_APP_CERTIFICATE_PASSWORD }} | |
| run: | | |
| $keychain = Join-Path $env:RUNNER_TEMP 'macos-signing.keychain-db' | |
| $keychainPassword = (& uuidgen) | |
| $certificatePath = Join-Path $env:RUNNER_TEMP 'apple-certificate.p12' | |
| [System.IO.File]::WriteAllBytes($certificatePath, [Convert]::FromBase64String($env:APPLE_APP_DEV_ID_APP_CERTIFICATE)) | |
| & security create-keychain -p $keychainPassword $keychain | |
| & security set-keychain-settings -lut 21600 $keychain | |
| & security unlock-keychain -p $keychainPassword $keychain | |
| & security import $certificatePath -k $keychain -P $env:APPLE_APP_DEV_ID_APP_CERTIFICATE_PASSWORD ` | |
| -T /usr/bin/codesign -T /usr/bin/security | |
| & security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $keychainPassword $keychain | |
| & security list-keychain -d user -s $keychain login.keychain-db | |
| Remove-Item -LiteralPath $certificatePath -Force | |
| - name: Sign, package, and notarize | |
| if: steps.signing-mode.outputs.should_sign == 'true' | |
| env: | |
| APPLE_BOT_PASSWORD: ${{ secrets.APPLE_BOT_PASSWORD }} | |
| SOURCE_DATE_EPOCH: "1704067200" | |
| run: > | |
| pwsh -NoLogo -NoProfile -File scripts/Release-MacOsPackage.ps1 ${{ matrix.rid }} | |
| "${{ needs.release-metadata.outputs.release_version }}" | |
| artifacts/macos-packages | |
| artifacts/macos-signed-packages | |
| "Developer ID Application: Devolutions inc." | |
| - name: Package without notarization (dry run / forked PR) | |
| if: steps.signing-mode.outputs.should_sign != 'true' | |
| env: | |
| SOURCE_DATE_EPOCH: "1704067200" | |
| run: > | |
| pwsh -NoLogo -NoProfile -File scripts/Release-MacOsPackage.ps1 ${{ matrix.rid }} | |
| "${{ needs.release-metadata.outputs.release_version }}" | |
| artifacts/macos-packages | |
| artifacts/macos-signed-packages | |
| - name: Validate final package | |
| run: pwsh -NoLogo -NoProfile -File scripts/Test-MacOsPackage.ps1 ${{ matrix.rid }} artifacts/macos-signed-packages/*.zip | |
| - name: Upload final macOS package artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-${{ matrix.rid }}-signed-packages | |
| path: artifacts/macos-signed-packages | |
| if-no-files-found: error | |
| linux-packages: | |
| name: Linux packages ${{ matrix.rid }} | |
| runs-on: ubuntu-24.04 | |
| needs: | |
| - linux-managed | |
| - release-metadata | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - rid: linux-x64 | |
| appimage_arch: x86_64 | |
| runtime_sha256: 2fca8b443c92510f1483a883f60061ad09b46b978b2631c807cd873a47ec260d | |
| - rid: linux-arm64 | |
| appimage_arch: aarch64 | |
| runtime_sha256: 00cbdfcf917cc6c0ff6d3347d59e0ca1f7f45a6df1a428a0d6d8a78664d87444 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: 10.0.x | |
| - name: Install package inspection tools | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install --no-install-recommends \ | |
| appstream desktop-file-utils rpm squashfs-tools | |
| - name: Install ARM64 cross toolchain | |
| if: matrix.rid == 'linux-arm64' | |
| run: | | |
| sudo apt-get install --no-install-recommends \ | |
| binutils-aarch64-linux-gnu gcc-aarch64-linux-gnu \ | |
| libc6-dev-arm64-cross | |
| - name: Fetch pinned AppImage runtime | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| shell: pwsh | |
| run: | | |
| New-Item -ItemType Directory -Force -Path artifacts/tools | Out-Null | |
| gh release download 20251108 --repo AppImage/type2-runtime ` | |
| --pattern "runtime-${{ matrix.appimage_arch }}" ` | |
| --dir artifacts/tools | |
| $runtime = "artifacts/tools/runtime-${{ matrix.appimage_arch }}" | |
| $actualHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $runtime).Hash.ToLowerInvariant() | |
| if ($actualHash -ne "${{ matrix.runtime_sha256 }}") { | |
| throw "Runtime hash mismatch: expected ${{ matrix.runtime_sha256 }}, got $actualHash." | |
| } | |
| - name: NativeAOT publish | |
| shell: pwsh | |
| run: | | |
| $publishArgs = @( | |
| "src/Devolutions.Terminal/Devolutions.Terminal.csproj", | |
| "-c", "Release", | |
| "-r", "${{ matrix.rid }}", | |
| "--self-contained", "true", | |
| "-p:VersionPrefix=${{ needs.release-metadata.outputs.release_version }}", | |
| "-p:DebugSymbols=false", | |
| "-p:DebugType=None", | |
| "-p:NativeDebugSymbols=false", | |
| "-o", "artifacts/publish/${{ matrix.rid }}" | |
| ) | |
| if ("${{ matrix.rid }}" -eq "linux-arm64") { | |
| $publishArgs += "-p:ObjCopyName=aarch64-linux-gnu-objcopy" | |
| } | |
| dotnet publish @publishArgs | |
| - name: Upload Linux NativeAOT publish | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-${{ matrix.rid }} | |
| path: artifacts/publish/${{ matrix.rid }} | |
| if-no-files-found: error | |
| - name: Build deterministic tar, DEB, RPM, and AppImage | |
| env: | |
| APPIMAGE_RUNTIME_FILE: ${{ github.workspace }}/artifacts/tools/runtime-${{ matrix.appimage_arch }} | |
| LINUX_PUBLISH_DIR: ${{ github.workspace }}/artifacts/publish/${{ matrix.rid }} | |
| SOURCE_DATE_EPOCH: "1704067200" | |
| run: | | |
| bash scripts/Build-LinuxPackage.sh \ | |
| "${{ matrix.rid }}" "${{ needs.release-metadata.outputs.release_version }}" \ | |
| artifacts/linux-packages all | |
| - name: Validate packages without launching UI | |
| run: | | |
| bash scripts/Test-LinuxPackage.sh "${{ matrix.rid }}" \ | |
| artifacts/linux-packages/*-"${{ matrix.rid }}".tar.gz \ | |
| artifacts/linux-packages/*-"${{ matrix.rid }}".deb \ | |
| artifacts/linux-packages/*-"${{ matrix.rid }}".rpm \ | |
| artifacts/linux-packages/*-"${{ matrix.rid }}".AppImage | |
| - name: Rebuild and compare x64 packages | |
| if: matrix.rid == 'linux-x64' | |
| env: | |
| APPIMAGE_RUNTIME_FILE: ${{ github.workspace }}/artifacts/tools/runtime-${{ matrix.appimage_arch }} | |
| LINUX_PUBLISH_DIR: ${{ github.workspace }}/artifacts/publish/${{ matrix.rid }} | |
| SOURCE_DATE_EPOCH: "1704067200" | |
| run: | | |
| bash scripts/Build-LinuxPackage.sh \ | |
| "${{ matrix.rid }}" "${{ needs.release-metadata.outputs.release_version }}" \ | |
| artifacts/linux-packages-rebuilt all | |
| cmp artifacts/linux-packages/*-"${{ matrix.rid }}".sha256 \ | |
| artifacts/linux-packages-rebuilt/*-"${{ matrix.rid }}".sha256 | |
| - name: Upload Linux package artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-${{ matrix.rid }}-packages | |
| path: artifacts/linux-packages | |
| if-no-files-found: error | |
| linux-arm64-hardware: | |
| name: Linux ARM64 native runtime (ubuntu-24.04-arm) | |
| runs-on: ubuntu-24.04-arm | |
| needs: linux-packages | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Verify native ARM64 runner | |
| shell: pwsh | |
| run: | | |
| $machine = uname -m | |
| if ($machine -notin @("aarch64", "arm64")) { | |
| Write-Error "::error::linux-arm64-hardware requires native ARM64; uname -m returned '$machine'. QEMU is not an accepted fallback." | |
| exit 78 | |
| } | |
| - name: Set up .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: 10.0.x | |
| - name: Install package inspection tools | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install --no-install-recommends \ | |
| appstream cpio desktop-file-utils rpm squashfs-tools | |
| - name: Download ARM64 packages | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-linux-arm64-packages | |
| path: artifacts/linux-arm64-packages | |
| - name: Run native non-UI ARM64 gates | |
| shell: bash | |
| run: bash scripts/Test-LinuxArm64Runtime.sh artifacts/linux-arm64-packages | |
| msix: | |
| name: Unsigned MSIX packages | |
| needs: | |
| - native-aot | |
| - release-metadata | |
| runs-on: windows-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up WinApp CLI | |
| uses: microsoft/setup-WinAppCli@v0.1 | |
| with: | |
| version: v0.6.0 | |
| - name: Download x64 publish | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-win-x64 | |
| path: artifacts/msix/layout/win-x64 | |
| - name: Download arm64 publish | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-win-arm64 | |
| path: artifacts/msix/layout/win-arm64 | |
| - name: Build unsigned MSIX packages | |
| shell: pwsh | |
| run: > | |
| ./src/Devolutions.Terminal.Package/Scripts/Build-Packages.ps1 | |
| -SkipPublish | |
| -OutputDirectory ./artifacts/msix | |
| -Version "${{ needs.release-metadata.outputs.msix_version }}" | |
| - name: Validate package structure | |
| shell: pwsh | |
| run: > | |
| ./src/Devolutions.Terminal.Package/Scripts/Test-Packages.ps1 | |
| -PackagePath (Get-ChildItem ./artifacts/msix/packages -File | | |
| Where-Object Extension -eq ".msix" | | |
| ForEach-Object FullName) | |
| - name: Upload unsigned MSIX artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-MSIX-unsigned | |
| path: artifacts/msix/packages | |
| if-no-files-found: error | |
| nuget: | |
| name: NuGet distribution package | |
| needs: | |
| - native-aot | |
| - linux-packages | |
| - macos-native-aot | |
| - release-metadata | |
| runs-on: windows-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: 10.0.x | |
| - name: Download Windows x64 publish | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-win-x64 | |
| path: artifacts/nuget/layout/win-x64 | |
| - name: Download Windows arm64 publish | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-win-arm64 | |
| path: artifacts/nuget/layout/win-arm64 | |
| - name: Download Linux x64 publish | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-linux-x64 | |
| path: artifacts/nuget/layout/linux-x64 | |
| - name: Download Linux arm64 publish | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-linux-arm64 | |
| path: artifacts/nuget/layout/linux-arm64 | |
| - name: Download macOS x64 publish | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-osx-x64 | |
| path: artifacts/nuget/layout/osx-x64 | |
| - name: Download macOS arm64 publish | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-osx-arm64 | |
| path: artifacts/nuget/layout/osx-arm64 | |
| - name: Build NuGet distribution package | |
| shell: pwsh | |
| run: > | |
| ./src/Devolutions.Terminal.Package/Scripts/Build-NuGet.ps1 | |
| -SkipPublish | |
| -Version "${{ needs.release-metadata.outputs.release_version }}" | |
| - name: Smoke test NuGet package import | |
| shell: pwsh | |
| run: > | |
| ./src/Devolutions.Terminal.Package/Scripts/Test-NuGetDistribution.ps1 | |
| -PackagePath ./artifacts/nuget/packages/Devolutions.Terminal.App.${{ needs.release-metadata.outputs.release_version }}.nupkg | |
| -Version "${{ needs.release-metadata.outputs.release_version }}" | |
| - name: Upload NuGet distribution package | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-NuGet | |
| path: artifacts/nuget/packages/*.nupkg | |
| if-no-files-found: error | |
| msi: | |
| name: MSI packages | |
| needs: | |
| - native-aot | |
| - release-metadata | |
| runs-on: windows-latest | |
| permissions: | |
| contents: read | |
| id-token: write | |
| environment: | |
| # Ordinary (non-release) builds are bound to the low-stakes dry-run environment so they never | |
| # wait on approval/protection rules meant for real signing releases; only an actual release | |
| # trigger (workflow_dispatch or a tag push) is bound to the real signing environment. | |
| name: ${{ (github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/')) && needs.release-metadata.outputs.publish_environment || 'publish-dry-run' }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Download x64 publish | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-win-x64 | |
| path: artifacts/msi/layout/win-x64 | |
| - name: Download arm64 publish | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-win-arm64 | |
| path: artifacts/msi/layout/win-arm64 | |
| - name: Set up WinApp CLI | |
| uses: microsoft/setup-WinAppCli@v0.1 | |
| with: | |
| version: v0.6.0 | |
| - name: Resolve binary signing mode | |
| id: signing-mode | |
| shell: pwsh | |
| env: | |
| IS_RELEASE_EVENT: ${{ github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/') }} | |
| AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} | |
| AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} | |
| AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | |
| TRUSTED_SIGNING_ENDPOINT: ${{ secrets.TRUSTED_SIGNING_ENDPOINT }} | |
| TRUSTED_SIGNING_ACCOUNT_NAME: ${{ secrets.TRUSTED_SIGNING_ACCOUNT_NAME }} | |
| TRUSTED_SIGNING_PROFILE_NAME: ${{ secrets.TRUSTED_SIGNING_PROFILE_NAME }} | |
| run: | | |
| # Ordinary (non-release) CI runs never attempt binary signing: they are not gated on | |
| # signing secrets being configured, so they must not fail when those secrets are absent. | |
| if ($env:IS_RELEASE_EVENT -ne "true") { | |
| "should_sign=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append | |
| Write-Host "Not a release build; skipping binary signing." | |
| exit 0 | |
| } | |
| $dryRun = [System.Boolean]::Parse('${{ needs.release-metadata.outputs.dry_run }}') | |
| $signDryRun = [System.Boolean]::Parse('${{ needs.release-metadata.outputs.sign_dry_run }}') | |
| $required = @( | |
| "AZURE_CLIENT_ID", | |
| "AZURE_TENANT_ID", | |
| "AZURE_SUBSCRIPTION_ID", | |
| "TRUSTED_SIGNING_ENDPOINT", | |
| "TRUSTED_SIGNING_ACCOUNT_NAME", | |
| "TRUSTED_SIGNING_PROFILE_NAME" | |
| ) | |
| $missing = @($required | Where-Object { [string]::IsNullOrWhiteSpace([Environment]::GetEnvironmentVariable($_)) }) | |
| if ($dryRun -and -not $signDryRun) { | |
| "should_sign=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append | |
| Write-Host "Dry run will not sign the application binaries bundled in the MSI." | |
| exit 0 | |
| } | |
| if ($missing.Count -gt 0) { | |
| if ($dryRun) { | |
| "should_sign=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append | |
| Write-Host "::notice::Skipping dry-run binary signing because these secrets are unavailable: $($missing -join ', ')" | |
| exit 0 | |
| } | |
| throw "Missing Azure Artifact Signing secrets: $($missing -join ', ')" | |
| } | |
| "should_sign=true" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append | |
| - name: Install Windows psign-tool | |
| if: steps.signing-mode.outputs.should_sign == 'true' | |
| shell: pwsh | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| $toolRoot = Join-Path $env:RUNNER_TEMP "psign-tool" | |
| if (Test-Path -LiteralPath $toolRoot) { | |
| Remove-Item -LiteralPath $toolRoot -Recurse -Force | |
| } | |
| New-Item -Path $toolRoot -ItemType Directory -Force | Out-Null | |
| gh release download --repo Devolutions/psign --pattern "psign-tool-windows-x64.zip" --dir $toolRoot --clobber | |
| $toolArchivePath = Join-Path $toolRoot "psign-tool-windows-x64.zip" | |
| if (-not (Test-Path -LiteralPath $toolArchivePath -PathType Leaf)) { | |
| throw "psign-tool archive was not found at $toolArchivePath" | |
| } | |
| Expand-Archive -Path $toolArchivePath -DestinationPath $toolRoot -Force | |
| $toolPath = Join-Path $toolRoot "psign-tool.exe" | |
| if (-not (Test-Path -LiteralPath $toolPath -PathType Leaf)) { | |
| throw "psign-tool executable was not found at $toolPath" | |
| } | |
| $toolRoot | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append | |
| & $toolPath --version | |
| - name: Azure login for Trusted Signing | |
| if: steps.signing-mode.outputs.should_sign == 'true' | |
| uses: azure/login@v2 | |
| with: | |
| client-id: ${{ secrets.AZURE_CLIENT_ID }} | |
| tenant-id: ${{ secrets.AZURE_TENANT_ID }} | |
| subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | |
| - name: Sign application binaries bundled in the MSI | |
| if: steps.signing-mode.outputs.should_sign == 'true' | |
| shell: pwsh | |
| env: | |
| TRUSTED_SIGNING_ENDPOINT: ${{ secrets.TRUSTED_SIGNING_ENDPOINT }} | |
| TRUSTED_SIGNING_ACCOUNT_NAME: ${{ secrets.TRUSTED_SIGNING_ACCOUNT_NAME }} | |
| TRUSTED_SIGNING_PROFILE_NAME: ${{ secrets.TRUSTED_SIGNING_PROFILE_NAME }} | |
| TRUSTED_SIGNING_TIMESTAMP_SERVER: ${{ vars.TRUSTED_SIGNING_TIMESTAMP_SERVER }} | |
| run: | | |
| $timestampServer = $env:TRUSTED_SIGNING_TIMESTAMP_SERVER | |
| if ([string]::IsNullOrWhiteSpace($timestampServer)) { | |
| $timestampServer = "http://timestamp.acs.microsoft.com/" | |
| } | |
| $accessToken = az account get-access-token ` | |
| --scope https://codesigning.azure.net/.default ` | |
| --query accessToken ` | |
| --output tsv | |
| if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($accessToken)) { | |
| throw "Failed to acquire an Azure Trusted Signing access token." | |
| } | |
| ./src/Devolutions.Terminal.Package/Scripts/Sign-Packages.ps1 ` | |
| -BinaryDirectory artifacts/msi/layout/win-x64, artifacts/msi/layout/win-arm64 ` | |
| -Version "${{ needs.release-metadata.outputs.msix_version }}" ` | |
| -ArtifactSigningEndpoint $env:TRUSTED_SIGNING_ENDPOINT ` | |
| -ArtifactSigningAccountName $env:TRUSTED_SIGNING_ACCOUNT_NAME ` | |
| -ArtifactSigningProfileName $env:TRUSTED_SIGNING_PROFILE_NAME ` | |
| -ArtifactSigningAccessToken $accessToken ` | |
| -TimestampServer $timestampServer | |
| - name: Build MSI packages | |
| shell: pwsh | |
| run: > | |
| ./src/Devolutions.Terminal.Package/Scripts/Build-Msi.ps1 | |
| -SkipPublish | |
| -OutputDirectory ./artifacts/msi | |
| -Version "${{ needs.release-metadata.outputs.msix_version }}" | |
| - name: Validate MSI package structure | |
| shell: pwsh | |
| env: | |
| SHOULD_SIGN: ${{ steps.signing-mode.outputs.should_sign }} | |
| run: | | |
| $packagePaths = Get-ChildItem ./artifacts/msi/packages -File | | |
| Where-Object Extension -eq ".msi" | | |
| ForEach-Object FullName | |
| $arguments = @{ | |
| PackagePath = $packagePaths | |
| } | |
| if ($env:SHOULD_SIGN -eq "true") { | |
| $arguments["RequireBinarySignature"] = $true | |
| } | |
| ./src/Devolutions.Terminal.Package/Scripts/Test-Packages.ps1 @arguments | |
| - name: Upload unsigned MSI artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-MSI-unsigned | |
| path: artifacts/msi/packages | |
| if-no-files-found: error | |
| release: | |
| name: Publish GitHub release | |
| if: ${{ github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/') }} | |
| needs: | |
| - build | |
| - native-aot | |
| - linux-managed | |
| - macos-managed | |
| - macos-native-aot | |
| - macos-sign | |
| - linux-packages | |
| - linux-arm64-hardware | |
| - msix | |
| - msi | |
| - nuget | |
| - release-metadata | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| id-token: write | |
| environment: | |
| name: ${{ needs.release-metadata.outputs.publish_environment }} | |
| env: | |
| RELEASE_TAG: ${{ needs.release-metadata.outputs.release_tag }} | |
| MSIX_VERSION: ${{ needs.release-metadata.outputs.msix_version }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Download Windows unsigned MSIX | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-MSIX-unsigned | |
| path: artifacts/msix-packages | |
| - name: Download Windows unsigned MSI | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-MSI-unsigned | |
| path: artifacts/msi-packages | |
| - name: Download Linux x64 packages | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-linux-x64-packages | |
| path: artifacts/linux-x64 | |
| - name: Download Linux arm64 packages | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-linux-arm64-packages | |
| path: artifacts/linux-arm64 | |
| - name: Download macOS arm64 package artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-osx-arm64-signed-packages | |
| path: artifacts/macos-packages | |
| - name: Download macOS x64 package artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-osx-x64-signed-packages | |
| path: artifacts/macos-packages | |
| - name: Download NuGet distribution package | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-NuGet | |
| path: artifacts/nuget | |
| - name: NuGet login (OIDC) | |
| if: needs.release-metadata.outputs.dry_run != 'true' | |
| id: nuget-login | |
| uses: NuGet/login@v1 | |
| with: | |
| user: ${{ secrets.NUGET_BOT_USERNAME }} | |
| - name: Publish NuGet distribution package | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $dryRun = [System.Boolean]::Parse('${{ needs.release-metadata.outputs.dry_run }}') | |
| $packages = @(Get-ChildItem -LiteralPath artifacts/nuget -Filter "*.nupkg" -File | Sort-Object Name) | |
| if ($packages.Count -ne 1) { | |
| throw "Expected exactly one NuGet package, found $($packages.Count)." | |
| } | |
| $packagePath = $packages[0].FullName | |
| if ($dryRun) { | |
| Write-Host "Dry run: skipping NuGet.org publication of $packagePath." | |
| exit 0 | |
| } | |
| $nugetApiKey = '${{ steps.nuget-login.outputs.NUGET_API_KEY }}' | |
| if ([string]::IsNullOrWhiteSpace($nugetApiKey)) { | |
| throw "NuGet/login did not provide a NuGet API key." | |
| } | |
| & dotnet nuget push $packagePath ` | |
| --api-key $nugetApiKey ` | |
| --source $env:NUGET_PACKAGE_SOURCE ` | |
| --skip-duplicate ` | |
| --no-symbols | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "dotnet nuget push failed for '$packagePath' with exit code $LASTEXITCODE." | |
| } | |
| - name: Resolve signing mode | |
| id: signing-mode | |
| shell: pwsh | |
| env: | |
| AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} | |
| AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} | |
| AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | |
| TRUSTED_SIGNING_ENDPOINT: ${{ secrets.TRUSTED_SIGNING_ENDPOINT }} | |
| TRUSTED_SIGNING_ACCOUNT_NAME: ${{ secrets.TRUSTED_SIGNING_ACCOUNT_NAME }} | |
| TRUSTED_SIGNING_PROFILE_NAME: ${{ secrets.TRUSTED_SIGNING_PROFILE_NAME }} | |
| run: | | |
| $dryRun = [System.Boolean]::Parse('${{ needs.release-metadata.outputs.dry_run }}') | |
| $signDryRun = [System.Boolean]::Parse('${{ needs.release-metadata.outputs.sign_dry_run }}') | |
| $required = @( | |
| "AZURE_CLIENT_ID", | |
| "AZURE_TENANT_ID", | |
| "AZURE_SUBSCRIPTION_ID", | |
| "TRUSTED_SIGNING_ENDPOINT", | |
| "TRUSTED_SIGNING_ACCOUNT_NAME", | |
| "TRUSTED_SIGNING_PROFILE_NAME" | |
| ) | |
| $missing = @($required | Where-Object { [string]::IsNullOrWhiteSpace([Environment]::GetEnvironmentVariable($_)) }) | |
| if ($dryRun -and -not $signDryRun) { | |
| "should_sign=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append | |
| Write-Host "Dry run will not sign Windows packages." | |
| exit 0 | |
| } | |
| if ($missing.Count -gt 0) { | |
| if ($dryRun) { | |
| "should_sign=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append | |
| Write-Host "::notice::Skipping dry-run signing because these secrets are unavailable: $($missing -join ', ')" | |
| exit 0 | |
| } | |
| throw "Missing Azure Artifact Signing secrets: $($missing -join ', ')" | |
| } | |
| "should_sign=true" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append | |
| - name: Install Linux psign-tool | |
| if: steps.signing-mode.outputs.should_sign == 'true' | |
| shell: pwsh | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| $toolRoot = Join-Path $env:RUNNER_TEMP "psign-tool" | |
| $extractRoot = Join-Path $toolRoot "expanded" | |
| if (Test-Path -LiteralPath $toolRoot) { | |
| Remove-Item -LiteralPath $toolRoot -Recurse -Force | |
| } | |
| New-Item -Path $extractRoot -ItemType Directory -Force | Out-Null | |
| gh release download --repo Devolutions/psign --pattern "psign-tool-linux-x64.zip" --dir $toolRoot --clobber | |
| $toolArchivePath = Join-Path $toolRoot "psign-tool-linux-x64.zip" | |
| if (-not (Test-Path -LiteralPath $toolArchivePath -PathType Leaf)) { | |
| throw "psign-tool archive was not found at $toolArchivePath" | |
| } | |
| Expand-Archive -Path $toolArchivePath -DestinationPath $extractRoot -Force | |
| $toolPath = Join-Path $extractRoot "psign-tool" | |
| if (-not (Test-Path -LiteralPath $toolPath -PathType Leaf)) { | |
| throw "psign-tool executable was not found at $toolPath" | |
| } | |
| chmod +x $toolPath | |
| $extractRoot | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append | |
| & $toolPath --version | |
| - name: Azure login for Trusted Signing | |
| if: steps.signing-mode.outputs.should_sign == 'true' | |
| uses: azure/login@v2 | |
| with: | |
| client-id: ${{ secrets.AZURE_CLIENT_ID }} | |
| tenant-id: ${{ secrets.AZURE_TENANT_ID }} | |
| subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | |
| - name: Sign Windows packages with Azure Artifact Signing | |
| if: steps.signing-mode.outputs.should_sign == 'true' | |
| shell: pwsh | |
| env: | |
| TRUSTED_SIGNING_ENDPOINT: ${{ secrets.TRUSTED_SIGNING_ENDPOINT }} | |
| TRUSTED_SIGNING_ACCOUNT_NAME: ${{ secrets.TRUSTED_SIGNING_ACCOUNT_NAME }} | |
| TRUSTED_SIGNING_PROFILE_NAME: ${{ secrets.TRUSTED_SIGNING_PROFILE_NAME }} | |
| TRUSTED_SIGNING_TIMESTAMP_SERVER: ${{ vars.TRUSTED_SIGNING_TIMESTAMP_SERVER }} | |
| run: | | |
| $timestampServer = $env:TRUSTED_SIGNING_TIMESTAMP_SERVER | |
| if ([string]::IsNullOrWhiteSpace($timestampServer)) { | |
| $timestampServer = "http://timestamp.acs.microsoft.com/" | |
| } | |
| $accessToken = az account get-access-token ` | |
| --scope https://codesigning.azure.net/.default ` | |
| --query accessToken ` | |
| --output tsv | |
| if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($accessToken)) { | |
| throw "Failed to acquire an Azure Trusted Signing access token." | |
| } | |
| ./src/Devolutions.Terminal.Package/Scripts/Sign-Packages.ps1 ` | |
| -PackageDirectory ./artifacts/msix-packages ` | |
| -Version $env:MSIX_VERSION ` | |
| -ArtifactSigningEndpoint $env:TRUSTED_SIGNING_ENDPOINT ` | |
| -ArtifactSigningAccountName $env:TRUSTED_SIGNING_ACCOUNT_NAME ` | |
| -ArtifactSigningProfileName $env:TRUSTED_SIGNING_PROFILE_NAME ` | |
| -ArtifactSigningAccessToken $accessToken ` | |
| -TimestampServer $timestampServer | |
| ./src/Devolutions.Terminal.Package/Scripts/Sign-Packages.ps1 ` | |
| -PackageDirectory ./artifacts/msi-packages ` | |
| -Version $env:MSIX_VERSION ` | |
| -ArtifactSigningEndpoint $env:TRUSTED_SIGNING_ENDPOINT ` | |
| -ArtifactSigningAccountName $env:TRUSTED_SIGNING_ACCOUNT_NAME ` | |
| -ArtifactSigningProfileName $env:TRUSTED_SIGNING_PROFILE_NAME ` | |
| -ArtifactSigningAccessToken $accessToken ` | |
| -TimestampServer $timestampServer | |
| - name: Stage release assets | |
| shell: pwsh | |
| run: | | |
| New-Item -ItemType Directory -Force -Path artifacts/release | Out-Null | |
| $artifactDirectories = @( | |
| "artifacts/msix-packages", | |
| "artifacts/msi-packages", | |
| "artifacts/linux-x64", | |
| "artifacts/linux-arm64", | |
| "artifacts/macos-packages", | |
| "artifacts/nuget" | |
| ) | |
| foreach ($directory in $artifactDirectories) { | |
| Get-ChildItem -LiteralPath $directory -File | Copy-Item -Destination artifacts/release -Force | |
| } | |
| Get-ChildItem -LiteralPath artifacts/release -File | Select-Object -ExpandProperty Name | |
| - name: Upload dry-run release assets | |
| if: needs.release-metadata.outputs.dry_run == 'true' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: DevolutionsTerminal-${{ env.RELEASE_TAG }}-dry-run | |
| path: artifacts/release | |
| if-no-files-found: error | |
| - name: Publish release to GitHub Releases | |
| if: needs.release-metadata.outputs.dry_run != 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| shell: pwsh | |
| run: | | |
| gh release view $env:RELEASE_TAG *> $null | |
| if ($LASTEXITCODE -ne 0) { | |
| gh release create $env:RELEASE_TAG ` | |
| --title "Devolutions Terminal $($env:RELEASE_TAG.TrimStart('v'))" ` | |
| --generate-notes ` | |
| --target "${{ github.sha }}" | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "Failed to create GitHub release $env:RELEASE_TAG." | |
| } | |
| } | |
| $assets = Get-ChildItem -LiteralPath artifacts/release -File | ForEach-Object FullName | |
| gh release upload $env:RELEASE_TAG @assets --clobber | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "Failed to upload release assets." | |
| } |