Skip to content

Keep telemetry trim-safe #93

Keep telemetry trim-safe

Keep telemetry trim-safe #93

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/**