Keep telemetry trim-safe #93
Workflow file for this run
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 & Release | |
| on: | |
| push: | |
| branches: [main] | |
| tags: ['v*'] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| inputs: | |
| signing: | |
| description: 'Signing mode: auto = PFX when secret exists, else self-signed' | |
| type: choice | |
| options: | |
| - auto | |
| - self-signed | |
| - pfx | |
| - none | |
| default: 'auto' | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| DOTNET_VERSION: '9.0.x' | |
| AVALONIA_PROJECT: 'src/GregModmanager.Avalonia/GregModmanager.Avalonia.csproj' | |
| CORE_PROJECT: 'src/GregModmanager.Core/GregModmanager.Core.csproj' | |
| SOLUTION: 'GregModmanager.sln' | |
| permissions: | |
| contents: read | |
| attestations: write | |
| id-token: write | |
| jobs: | |
| version: | |
| name: Resolve Version | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| outputs: | |
| version: ${{ steps.resolve.outputs.version }} | |
| numeric_version: ${{ steps.resolve.outputs.numeric_version }} | |
| is_release: ${{ steps.resolve.outputs.is_release }} | |
| is_pre: ${{ steps.resolve.outputs.is_pre }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - id: resolve | |
| shell: bash | |
| run: | | |
| ver=$(sed -n 's:.*<Version>\([^<]*\)</Version>.*:\1:p' "$AVALONIA_PROJECT" | head -n 1 | tr -d '\r' | xargs) | |
| [ -n "$ver" ] || ver='1.5.0' | |
| numeric="${ver%%-*}" | |
| if [[ "$numeric" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | |
| numeric="$numeric.0" | |
| elif [[ ! "$numeric" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | |
| numeric='1.5.0.0' | |
| fi | |
| is_release=false | |
| [[ "$GITHUB_REF_NAME" =~ ^v[0-9] ]] && is_release=true | |
| is_pre=false | |
| [[ "$ver" == *-* || "$GITHUB_REF_NAME" == *-* ]] && is_pre=true | |
| { | |
| echo "version=$ver" | |
| echo "numeric_version=$numeric" | |
| echo "is_release=$is_release" | |
| echo "is_pre=$is_pre" | |
| } >> "$GITHUB_OUTPUT" | |
| echo "Version=$ver Numeric=$numeric IsRelease=$is_release IsPre=$is_pre" | |
| test: | |
| name: Test | |
| needs: [version] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }} | |
| - uses: actions/cache@v4 | |
| with: | |
| path: ~/.nuget/packages | |
| key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj') }} | |
| restore-keys: nuget-${{ runner.os }}- | |
| - run: dotnet restore ${{ env.SOLUTION }} | |
| - run: dotnet build ${{ env.SOLUTION }} --no-restore -c Release | |
| - run: dotnet test ${{ env.SOLUTION }} --no-build -c Release --verbosity normal | |
| build-windows: | |
| name: Build Windows | |
| needs: [version, test] | |
| if: github.event_name != 'pull_request' || github.event.pull_request.draft == false | |
| runs-on: windows-latest | |
| timeout-minutes: 20 | |
| permissions: | |
| contents: read | |
| attestations: write | |
| id-token: write | |
| outputs: | |
| setup_name: ${{ steps.package.outputs.setup_name }} | |
| portable_name: ${{ steps.package.outputs.portable_name }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }} | |
| - uses: actions/cache@v4 | |
| with: | |
| path: ~/.nuget/packages | |
| key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj') }} | |
| restore-keys: nuget-${{ runner.os }}- | |
| - name: Install Inno Setup | |
| run: choco install innosetup --yes --no-progress | |
| - name: Determine signing mode | |
| id: signing | |
| shell: pwsh | |
| env: | |
| PFX_BASE64: ${{ secrets.CODE_SIGN_PFX_BASE64 }} | |
| SIGNING_INPUT: ${{ inputs.signing || 'auto' }} | |
| run: | | |
| $mode = $env:SIGNING_INPUT | |
| $hasPfx = -not [string]::IsNullOrWhiteSpace($env:PFX_BASE64) | |
| if ($mode -eq 'auto') { $mode = if ($hasPfx) { 'pfx' } else { 'self-signed' } } | |
| if ($mode -eq 'none') { $mode = '' } | |
| "signing_mode=$mode" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 | |
| Write-Host "Signing mode: $mode" | |
| - name: Decode PFX certificate | |
| if: steps.signing.outputs.signing_mode == 'pfx' | |
| shell: pwsh | |
| env: | |
| PFX_BASE64: ${{ secrets.CODE_SIGN_PFX_BASE64 }} | |
| PFX_PWD: ${{ secrets.CODE_SIGN_PFX_PASSWORD }} | |
| run: | | |
| $pfxBytes = [System.Convert]::FromBase64String($env:PFX_BASE64) | |
| $pfxPath = Join-Path $env:RUNNER_TEMP 'code-signing.pfx' | |
| [System.IO.File]::WriteAllBytes($pfxPath, $pfxBytes) | |
| "CODE_SIGN_PFX=$pfxPath" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 | |
| "CODE_SIGN_PFX_PASSWORD=$($env:PFX_PWD)" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 | |
| - name: Create self-signed certificate | |
| if: steps.signing.outputs.signing_mode == 'self-signed' | |
| shell: pwsh | |
| run: | | |
| $cert = New-SelfSignedCertificate -Type CodeSigningCert -Subject "CN=GregTools CI" ` | |
| -KeyUsage DigitalSignature -KeyAlgorithm RSA -KeyLength 2048 -HashAlgorithm SHA256 ` | |
| -NotAfter (Get-Date).AddDays(7) -CertStoreLocation Cert:\CurrentUser\My -FriendlyName "GregTools CI" | |
| "CODE_SIGN_THUMBPRINT=$($cert.Thumbprint)" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 | |
| - name: Build & Package | |
| id: package | |
| shell: pwsh | |
| env: | |
| VERSION: ${{ needs.version.outputs.version }} | |
| NUMERIC_VERSION: ${{ needs.version.outputs.numeric_version }} | |
| IS_PRE: ${{ needs.version.outputs.is_pre }} | |
| TELEMETRY_ENDPOINT: ${{ secrets.TELEMETRY_ENDPOINT }} | |
| TELEMETRY_USER: ${{ secrets.TELEMETRY_USER }} | |
| TELEMETRY_API_KEY: ${{ secrets.TELEMETRY_API_KEY }} | |
| TELEMETRY_TENANT: ${{ secrets.TELEMETRY_TENANT }} | |
| run: | | |
| $sign = if ($env:CODE_SIGN_PFX -or $env:CODE_SIGN_THUMBPRINT) { $true } else { $false } | |
| if ($sign) { | |
| .\build\scripts\build.ps1 -Sign -SkipTest -SkipLinux | |
| } else { | |
| .\build\scripts\build.ps1 -SkipTest -SkipLinux | |
| } | |
| $preSuffix = if ($env:IS_PRE -eq 'true') { '-pre' } else { '' } | |
| $setup = Get-ChildItem -Path ("build\installer\Output\gregModmanager-*{0}-Windows.exe" -f $preSuffix) | Select-Object -First 1 | |
| $portable = Get-ChildItem -Path ("build\installer\Output\gregModmanager-*{0}-Windows.zip" -f $preSuffix) | Select-Object -First 1 | |
| if (-not $setup) { throw "Setup EXE not found" } | |
| if (-not $portable) { throw "Portable ZIP not found" } | |
| "setup_name=$($setup.Name)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 | |
| "portable_name=$($portable.Name)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 | |
| Write-Host "Setup: $($setup.Name)" | |
| Write-Host "Portable: $($portable.Name)" | |
| - name: Upload Setup | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: windows-setup | |
| path: | | |
| build/installer/Output/gregModmanager-*-Windows.exe | |
| build/installer/Output/gregModmanager-*-Windows.exe.* | |
| if-no-files-found: error | |
| - name: Upload Portable | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: windows-portable | |
| path: | | |
| build/installer/Output/gregModmanager-*-Windows.zip | |
| build/installer/Output/gregModmanager-*-Windows.zip.* | |
| if-no-files-found: error | |
| - name: Attest Windows Build | |
| if: github.event_name != 'pull_request' | |
| uses: actions/attest-build-provenance@v1 | |
| with: | |
| subject-path: | | |
| build/installer/Output/gregModmanager-*-Windows.exe | |
| build/installer/Output/gregModmanager-*-Windows.zip | |
| build-linux: | |
| name: Build Linux | |
| needs: [version, test] | |
| if: github.event_name != 'pull_request' || github.event.pull_request.draft == false | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| permissions: | |
| contents: read | |
| attestations: write | |
| id-token: write | |
| outputs: | |
| tar_name: ${{ steps.package.outputs.tar_name }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }} | |
| - uses: actions/cache@v4 | |
| with: | |
| path: ~/.nuget/packages | |
| key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj') }} | |
| restore-keys: nuget-${{ runner.os }}- | |
| - name: Telemetry Injection | |
| shell: pwsh | |
| env: | |
| TELEMETRY_ENDPOINT: ${{ secrets.TELEMETRY_ENDPOINT }} | |
| TELEMETRY_USER: ${{ secrets.TELEMETRY_USER }} | |
| TELEMETRY_API_KEY: ${{ secrets.TELEMETRY_API_KEY }} | |
| TELEMETRY_TENANT: ${{ secrets.TELEMETRY_TENANT }} | |
| run: | | |
| $secretsPath = 'src/GregModmanager.Core/Services/TelemetrySecrets.cs' | |
| $url = $env:TELEMETRY_ENDPOINT | |
| if (-not [string]::IsNullOrWhiteSpace($url)) { | |
| Write-Host "Injecting telemetry secrets..." | |
| $content = Get-Content -Raw $secretsPath | |
| $content = $content.Replace('__LOKI_URL__', $url) | |
| if (-not [string]::IsNullOrWhiteSpace($env:TELEMETRY_USER)) { $content = $content.Replace('__LOKI_USER__', $env:TELEMETRY_USER) } | |
| if (-not [string]::IsNullOrWhiteSpace($env:TELEMETRY_API_KEY)) { $content = $content.Replace('__LOKI_PASS__', $env:TELEMETRY_API_KEY) } | |
| if (-not [string]::IsNullOrWhiteSpace($env:TELEMETRY_TENANT)) { $content = $content.Replace('__LOKI_TENANT__', $env:TELEMETRY_TENANT) } | |
| Set-Content -Path $secretsPath -Value $content -Force | |
| } | |
| - name: Publish Linux x64 | |
| run: | | |
| dotnet publish ${{ env.AVALONIA_PROJECT }} -c Release -r linux-x64 \ | |
| --self-contained true /p:PublishTrimmed=true /p:TrimMode=full \ | |
| /p:PublishSingleFile=true /p:DebugType=none /p:DebugSymbols=false \ | |
| -o ./publish/linux-x64 | |
| - name: Create tarball | |
| id: package | |
| env: | |
| IS_PRE: ${{ needs.version.outputs.is_pre }} | |
| run: | | |
| mkdir -p ./artifacts | |
| chmod +x ./publish/linux-x64/GregModmanager | |
| preSuffix="" | |
| if [ "$IS_PRE" = "true" ]; then preSuffix="-pre"; fi | |
| tarName="gregModmanager-${{ needs.version.outputs.version }}${preSuffix}-Linux.tar.gz" | |
| tar -czf "./artifacts/${tarName}" -C ./publish/linux-x64 . | |
| echo "tar_name=${tarName}" >> "$GITHUB_OUTPUT" | |
| # Create Linux standalone ZIP | |
| zipName="gregModmanager-${{ needs.version.outputs.version }}${preSuffix}-Linux.zip" | |
| cd ./publish/linux-x64 && zip -r "../../artifacts/${zipName}" . && cd ../.. | |
| echo "zip_name=${zipName}" >> "$GITHUB_OUTPUT" | |
| - name: Upload tarball | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: linux-portable | |
| path: | | |
| artifacts/gregModmanager-*-Linux.tar.gz | |
| artifacts/gregModmanager-*-Linux.zip | |
| if-no-files-found: error | |
| - name: Attest Linux Build | |
| if: github.event_name != 'pull_request' | |
| uses: actions/attest-build-provenance@v1 | |
| with: | |
| subject-path: artifacts/gregModmanager-*-Linux.tar.gz | |
| build-linux-packages: | |
| name: Build Linux Packages (DEB/RPM/APK/Arch) | |
| needs: [version, build-linux] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }} | |
| - uses: actions/cache@v4 | |
| with: | |
| path: ~/.nuget/packages | |
| key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj') }} | |
| restore-keys: nuget-${{ runner.os }}- | |
| - name: Install nfpm | |
| run: | | |
| echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' | sudo tee /etc/apt/sources.list.d/goreleaser.list | |
| sudo apt update | |
| sudo apt install -y nfpm | |
| nfpm --version | |
| - name: Telemetry Injection | |
| shell: pwsh | |
| env: | |
| TELEMETRY_ENDPOINT: ${{ secrets.TELEMETRY_ENDPOINT }} | |
| TELEMETRY_USER: ${{ secrets.TELEMETRY_USER }} | |
| TELEMETRY_API_KEY: ${{ secrets.TELEMETRY_API_KEY }} | |
| TELEMETRY_TENANT: ${{ secrets.TELEMETRY_TENANT }} | |
| run: | | |
| $secretsPath = 'src/GregModmanager.Core/Services/TelemetrySecrets.cs' | |
| $url = $env:TELEMETRY_ENDPOINT | |
| if (-not [string]::IsNullOrWhiteSpace($url)) { | |
| Write-Host "Injecting telemetry secrets..." | |
| $content = Get-Content -Raw $secretsPath | |
| $content = $content.Replace('__LOKI_URL__', $url) | |
| if (-not [string]::IsNullOrWhiteSpace($env:TELEMETRY_USER)) { $content = $content.Replace('__LOKI_USER__', $env:TELEMETRY_USER) } | |
| if (-not [string]::IsNullOrWhiteSpace($env:TELEMETRY_API_KEY)) { $content = $content.Replace('__LOKI_PASS__', $env:TELEMETRY_API_KEY) } | |
| if (-not [string]::IsNullOrWhiteSpace($env:TELEMETRY_TENANT)) { $content = $content.Replace('__LOKI_TENANT__', $env:TELEMETRY_TENANT) } | |
| Set-Content -Path $secretsPath -Value $content -Force | |
| } | |
| - name: Publish Linux x64 | |
| run: | | |
| dotnet publish ${{ env.AVALONIA_PROJECT }} -c Release -r linux-x64 \ | |
| --self-contained true /p:PublishTrimmed=true /p:TrimMode=full \ | |
| /p:PublishSingleFile=true /p:DebugType=none /p:DebugSymbols=false \ | |
| -o ./artifacts/publish | |
| - name: Build packages | |
| run: | | |
| chmod +x ./build/scripts/linux/build-avalonia-packages.sh | |
| ./build/scripts/linux/build-avalonia-packages.sh ./artifacts ${{ needs.version.outputs.version }} ${{ needs.version.outputs.is_pre }} | |
| - name: Upload packages | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: linux-packages | |
| path: artifacts/packages/gregModmanager-* | |
| if-no-files-found: error | |
| - name: Build & Push GHCR Container | |
| if: github.event_name != 'pull_request' | |
| env: | |
| IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/gregmodmanager | |
| run: | | |
| echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin | |
| docker build -t "$IMAGE_NAME:${{ needs.version.outputs.version }}" -t "$IMAGE_NAME:latest" -f - . <<'EOF' | |
| FROM mcr.microsoft.com/dotnet/runtime-deps:9.0 | |
| WORKDIR /opt/gregmodmanager | |
| COPY artifacts/publish/ ./ | |
| RUN chmod +x /opt/gregmodmanager/GregModmanager | |
| ENTRYPOINT ["/opt/gregmodmanager/GregModmanager"] | |
| EOF | |
| docker push "$IMAGE_NAME:${{ needs.version.outputs.version }}" | |
| docker push "$IMAGE_NAME:latest" | |
| release: | |
| name: Release | |
| needs: [version, build-windows, build-linux, build-linux-packages] | |
| runs-on: ubuntu-latest | |
| if: needs.version.outputs.is_release == 'true' && github.event_name != 'pull_request' | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: write | |
| attestations: write | |
| steps: | |
| - name: Download Windows Setup | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: windows-setup | |
| path: ./dist | |
| - name: Download Windows Portable | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: windows-portable | |
| path: ./dist | |
| - name: Download Linux Portable | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: linux-portable | |
| path: ./dist | |
| - name: Download Linux Packages | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: linux-packages | |
| path: ./dist | |
| - name: Create Release | |
| uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 # v2.0.8 | |
| with: | |
| tag_name: ${{ github.ref_name }} | |
| prerelease: ${{ needs.version.outputs.is_pre == 'true' }} | |
| files: ./dist/** | |
| fail_on_unmatched_files: false | |
| generate_release_notes: true | |
| - name: Attest Release | |
| uses: actions/attest-build-provenance@v1 | |
| with: | |
| subject-path: ./dist/** |