Skip to content

Release

Release #13

Workflow file for this run

name: Release
on:
workflow_dispatch:
inputs:
tag:
description: "Git tag to release (e.g., v1.0.0) - Leave empty to auto-calculate from GitVersion"
required: false
type: string
create_docker:
description: "Also create Docker image"
required: false
default: true
type: boolean
env:
DOTNET_VERSION: "9.0.x"
PROJECT_NAME: "KnxMonitor"
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
jobs:
calculate-version:
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
version: ${{ steps.gitversion.outputs.semVer }}
tag: ${{ steps.determine-tag.outputs.tag }}
informational-version: ${{ steps.gitversion.outputs.informationalVersion }}
assembly-version: ${{ steps.gitversion.outputs.assemblySemVer }}
is-prerelease: ${{ steps.gitversion.outputs.preReleaseLabel != '' }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Install GitVersion
uses: gittools/actions/gitversion/setup@v0.10.2
with:
versionSpec: "6.x"
- name: Determine Version
id: gitversion
uses: gittools/actions/gitversion/execute@v0.10.2
with:
useConfigFile: true
- name: Determine Tag
id: determine-tag
run: |
if [[ -n "${{ needs.calculate-version.outputs.tag }}" ]]; then
# Use provided tag
TAG="${{ needs.calculate-version.outputs.tag }}"
echo "Using provided tag: $TAG"
else
# Auto-calculate tag from GitVersion
TAG="v${{ steps.gitversion.outputs.semVer }}"
echo "Auto-calculated tag: $TAG"
fi
echo "tag=$TAG" >> $GITHUB_OUTPUT
- name: Display GitVersion outputs
run: |
echo "SemVer: ${{ steps.gitversion.outputs.semVer }}"
echo "FullSemVer: ${{ steps.gitversion.outputs.fullSemVer }}"
echo "InformationalVersion: ${{ steps.gitversion.outputs.informationalVersion }}"
echo "AssemblySemVer: ${{ steps.gitversion.outputs.assemblySemVer }}"
echo "PreReleaseLabel: ${{ steps.gitversion.outputs.preReleaseLabel }}"
echo "Final Tag: ${{ steps.determine-tag.outputs.tag }}"
build:
needs: calculate-version
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
runtime: [win-x64, linux-x64, osx-x64, osx-arm64]
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Install GitVersion
uses: gittools/actions/gitversion/setup@v0.10.2
with:
versionSpec: "6.x"
- name: Determine Version
id: gitversion
uses: gittools/actions/gitversion/execute@v0.10.2
with:
useConfigFile: true
- name: Cache NuGet packages
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/Directory.Packages.props') }}
restore-keys: |
${{ runner.os }}-nuget-
- name: Restore dependencies
run: dotnet restore --verbosity minimal
- name: Run tests
run: dotnet test --configuration Release --verbosity normal --no-restore
- name: Build and publish
run: |
dotnet publish ${{ env.PROJECT_NAME }} \
--configuration Release \
--runtime ${{ matrix.runtime }} \
--self-contained true \
--output ./publish/${{ matrix.runtime }} \
-p:PublishSingleFile=true \
--verbosity minimal
- name: Create archive
run: |
cd ./publish/${{ matrix.runtime }}
if [[ "${{ matrix.runtime }}" == win-* ]]; then
zip -r ../../${{ env.PROJECT_NAME }}-${{ needs.calculate-version.outputs.version }}-${{ matrix.runtime }}.zip .
else
tar -czf ../../${{ env.PROJECT_NAME }}-${{ needs.calculate-version.outputs.version }}-${{ matrix.runtime }}.tar.gz .
fi
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ env.PROJECT_NAME }}-${{ matrix.runtime }}
path: |
*.zip
*.tar.gz
retention-days: 1
compression-level: 9
docker:
needs: [calculate-version, build]
if: inputs.create_docker
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository_owner }}/${{ env.PROJECT_NAME }}
tags: |
type=raw,value=${{ needs.calculate-version.outputs.version }}
type=raw,value=latest,enable=${{ needs.calculate-version.outputs.is-prerelease == 'false' }}
labels: |
org.opencontainers.image.title=KNX Monitor
org.opencontainers.image.description=KNX/EIB bus monitoring and debugging tool
org.opencontainers.image.vendor=metaneutrons
org.opencontainers.image.version=${{ needs.calculate-version.outputs.version }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.documentation=${{ github.server_url }}/${{ github.repository }}/blob/main/README.md
org.opencontainers.image.licenses=GPL-3.0
- name: Download Linux binary for Docker
uses: actions/download-artifact@v4
with:
name: ${{ env.PROJECT_NAME }}-linux-x64
path: ./docker-context
- name: Extract Linux binary and prepare Docker context
run: |
cd docker-context
tar -xzf ${{ env.PROJECT_NAME }}-${{ needs.calculate-version.outputs.version }}-linux-x64.tar.gz
mkdir -p publish
mv KnxMonitor publish/
cp ../Dockerfile .
ls -la publish/
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: ./docker-context
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
VERSION=${{ needs.calculate-version.outputs.version }}
BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}
VCS_REF=${{ github.sha }}
provenance: true
sbom: true
release:
needs: [calculate-version, build, docker]
if: always() && needs.calculate-version.result == 'success' && needs.build.result == 'success' && (needs.docker.result == 'success' || needs.docker.result == 'skipped')
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: ./artifacts
- name: Prepare release assets
run: |
mkdir -p ./release-assets
find ./artifacts -name "*.zip" -o -name "*.tar.gz" | xargs -I {} cp {} ./release-assets/
ls -la ./release-assets/
- name: Create and push Git tag
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -a ${{ needs.calculate-version.outputs.tag }} -m "Release ${{ needs.calculate-version.outputs.tag }}"
git push origin ${{ needs.calculate-version.outputs.tag }}
- name: Generate release notes
id: release_notes
run: |
cat << EOF > release_notes.md
## 🚀 KNX Monitor ${{ needs.calculate-version.outputs.version }}
### 📋 Version Information
- **Version**: ${{ needs.calculate-version.outputs.version }}
- **GitVersion**: ${{ needs.calculate-version.outputs.informational-version }}
- **Assembly Version**: ${{ needs.calculate-version.outputs.assembly-version }}
### 📦 Installation
#### Homebrew (macOS/Linux)
\`\`\`bash
brew install metaneutrons/tap/knxmonitor
\`\`\`
#### Docker
\`\`\`bash
docker run --rm -it ghcr.io/metaneutrons/knxmonitor:${{ needs.calculate-version.outputs.version }} --help
\`\`\`
#### Manual Installation
Download the appropriate binary for your platform from the assets below.
### 🏗️ Build Information
- **.NET Version**: 9.0
- **Build Date**: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
- **Commit**: ${{ github.sha }}
- **Tag**: ${{ needs.calculate-version.outputs.tag }}
### 📋 Supported Platforms
- **Windows x64** (knxmonitor-${{ needs.calculate-version.outputs.version }}-win-x64.zip)
- **Linux x64** (knxmonitor-${{ needs.calculate-version.outputs.version }}-linux-x64.tar.gz)
- **macOS x64** (knxmonitor-${{ needs.calculate-version.outputs.version }}-osx-x64.tar.gz)
- **macOS ARM64** (knxmonitor-${{ needs.calculate-version.outputs.version }}-osx-arm64.tar.gz)
- **Docker** (ghcr.io/metaneutrons/knxmonitor:${{ needs.calculate-version.outputs.version }})
### 🔧 Quick Start
\`\`\`bash
# Show version
knxmonitor --version
# Monitor KNX bus via IP routing
knxmonitor --connection-type routing
# Monitor with IP tunneling
knxmonitor --connection-type tunneling --host 192.168.1.100
\`\`\`
---
**Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ needs.calculate-version.outputs.tag }}...HEAD
EOF
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ needs.calculate-version.outputs.tag }}
name: "KNX Monitor ${{ needs.calculate-version.outputs.version }}"
body_path: release_notes.md
files: ./release-assets/*
draft: false
prerelease: ${{ needs.calculate-version.outputs.is-prerelease == 'true' }}
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
update-homebrew:
needs: [calculate-version, release, docker]
if: always() && needs.calculate-version.outputs.is-prerelease == 'false' && needs.release.result == 'success' && (needs.docker.result == 'success' || needs.docker.result == 'skipped')
runs-on: ubuntu-latest
timeout-minutes: 10
continue-on-error: false # Don't fail the entire release if Homebrew update fails
steps:
- name: Download macOS artifacts
uses: actions/download-artifact@v4
with:
pattern: ${{ env.PROJECT_NAME }}-osx-*
path: ./macos-artifacts
- name: Calculate checksums
id: checksums
run: |
# Calculate SHA256 for macOS x64
MACOS_X64_SHA=$(sha256sum ./macos-artifacts/${{ env.PROJECT_NAME }}-osx-x64/${{ env.PROJECT_NAME }}-${{ needs.calculate-version.outputs.version }}-osx-x64.tar.gz | cut -d' ' -f1)
echo "macos_x64_sha=${MACOS_X64_SHA}" >> $GITHUB_OUTPUT
# Calculate SHA256 for macOS ARM64
MACOS_ARM64_SHA=$(sha256sum ./macos-artifacts/${{ env.PROJECT_NAME }}-osx-arm64/${{ env.PROJECT_NAME }}-${{ needs.calculate-version.outputs.version }}-osx-arm64.tar.gz | cut -d' ' -f1)
echo "macos_arm64_sha=${MACOS_ARM64_SHA}" >> $GITHUB_OUTPUT
echo "macOS x64 SHA256: ${MACOS_X64_SHA}"
echo "macOS ARM64 SHA256: ${MACOS_ARM64_SHA}"
- name: Update Homebrew formula (if token available)
if: env.HOMEBREW_UPDATE_TOKEN != ''
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.HOMEBREW_UPDATE_TOKEN }}
repository: metaneutrons/homebrew-tap
event-type: update-formula
client-payload: |
{
"version": "${{ needs.calculate-version.outputs.version }}",
"macos_x64_sha": "${{ steps.checksums.outputs.macos_x64_sha }}",
"macos_arm64_sha": "${{ steps.checksums.outputs.macos_arm64_sha }}"
}
env:
HOMEBREW_UPDATE_TOKEN: ${{ secrets.HOMEBREW_UPDATE_TOKEN }}
- name: Create Homebrew update instructions (fallback)
if: env.HOMEBREW_UPDATE_TOKEN == ''
env:
HOMEBREW_UPDATE_TOKEN: ${{ secrets.HOMEBREW_UPDATE_TOKEN }}
run: |
cat << EOF > homebrew-update.md
## 🍺 Homebrew Formula Update Required
**Version**: ${{ needs.calculate-version.outputs.version }}
**macOS x64 SHA256**: \`${{ steps.checksums.outputs.macos_x64_sha }}\`
**macOS ARM64 SHA256**: \`${{ steps.checksums.outputs.macos_arm64_sha }}\`
### Manual Update Steps:
1. Go to [homebrew-tap repository](https://github.com/metaneutrons/homebrew-tap)
2. Edit \`knxmonitor.rb\`
3. Update version to: \`${{ needs.calculate-version.outputs.version }}\`
4. Update Intel Mac SHA256 to: \`${{ steps.checksums.outputs.macos_x64_sha }}\`
5. Update Apple Silicon SHA256 to: \`${{ steps.checksums.outputs.macos_arm64_sha }}\`
### Or run the update workflow manually:
\`\`\`bash
gh workflow run update-formula.yml \\
--repo metaneutrons/homebrew-tap \\
--field version=${{ needs.calculate-version.outputs.version }} \\
--field macos_x64_sha=${{ steps.checksums.outputs.macos_x64_sha }} \\
--field macos_arm64_sha=${{ steps.checksums.outputs.macos_arm64_sha }}
\`\`\`
EOF
- name: Upload Homebrew update instructions
uses: actions/upload-artifact@v4
with:
name: homebrew-update-instructions
path: homebrew-update.md
retention-days: 30
- name: Display update information
env:
HOMEBREW_UPDATE_TOKEN: ${{ secrets.HOMEBREW_UPDATE_TOKEN }}
run: |
echo "## 🍺 Homebrew Update Information" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Version**: ${{ needs.calculate-version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo "**macOS x64 SHA256**: \`${{ steps.checksums.outputs.macos_x64_sha }}\`" >> $GITHUB_STEP_SUMMARY
echo "**macOS ARM64 SHA256**: \`${{ steps.checksums.outputs.macos_arm64_sha }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [[ -n "$HOMEBREW_UPDATE_TOKEN" ]]; then
echo "✅ Homebrew formula updated automatically via repository dispatch." >> $GITHUB_STEP_SUMMARY
else
echo "ℹ️ Homebrew formula needs to be updated manually. Check the homebrew-update-instructions artifact." >> $GITHUB_STEP_SUMMARY
fi
- name: Release Summary
run: |
echo "## 🎉 Release Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Version**: ${{ needs.calculate-version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo "**Tag**: ${{ needs.calculate-version.outputs.tag }}" >> $GITHUB_STEP_SUMMARY
echo "**GitVersion**: ${{ needs.calculate-version.outputs.informational-version }}" >> $GITHUB_STEP_SUMMARY
echo "**Prerelease**: ${{ needs.calculate-version.outputs.is-prerelease }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 📦 Assets" >> $GITHUB_STEP_SUMMARY
echo "- $(ls -1 ./release-assets/ | wc -l) platform binaries" >> $GITHUB_STEP_SUMMARY
if [[ "${{ inputs.create_docker }}" == "true" ]]; then
echo "- Docker image: \`ghcr.io/metaneutrons/knxmonitor:${{ needs.calculate-version.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY
fi
if [[ "${{ needs.calculate-version.outputs.is-prerelease }}" == "false" ]]; then
echo "- Homebrew formula will be updated automatically" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 🔗 Links" >> $GITHUB_STEP_SUMMARY
echo "- [Release Page](https://github.com/${{ github.repository }}/releases/tag/${{ needs.calculate-version.outputs.tag }})" >> $GITHUB_STEP_SUMMARY
echo "- [Docker Image](https://github.com/${{ github.repository }}/pkgs/container/knxmonitor)" >> $GITHUB_STEP_SUMMARY
echo "- [Homebrew Tap](https://github.com/metaneutrons/homebrew-tap)" >> $GITHUB_STEP_SUMMARY
# Summary job to show overall status
summary:
needs: [calculate-version, build, docker, release, update-homebrew]
if: always()
runs-on: ubuntu-latest
steps:
- name: Release Summary
run: |
echo "## 🎉 Release Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Version**: ${{ needs.calculate-version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo "**Tag**: ${{ needs.calculate-version.outputs.tag }}" >> $GITHUB_STEP_SUMMARY
echo "**GitVersion**: ${{ needs.calculate-version.outputs.informational-version }}" >> $GITHUB_STEP_SUMMARY
echo "**Prerelease**: ${{ needs.calculate-version.outputs.is-prerelease }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 📊 Job Status" >> $GITHUB_STEP_SUMMARY
echo "- **Version Calculation**: ${{ needs.calculate-version.result == 'success' && '✅' || '❌' }}" >> $GITHUB_STEP_SUMMARY
echo "- **Build**: ${{ needs.build.result == 'success' && '✅' || '❌' }}" >> $GITHUB_STEP_SUMMARY
echo "- **Docker**: ${{ needs.docker.result == 'success' && '✅' || needs.docker.result == 'skipped' && '⏭️' || '❌' }}" >> $GITHUB_STEP_SUMMARY
echo "- **Release**: ${{ needs.release.result == 'success' && '✅' || '❌' }}" >> $GITHUB_STEP_SUMMARY
echo "- **Homebrew**: ${{ needs.update-homebrew.result == 'success' && '✅' || needs.update-homebrew.result == 'skipped' && '⏭️' || '❌' }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 📦 Assets" >> $GITHUB_STEP_SUMMARY
if [[ "${{ needs.build.result }}" == "success" ]]; then
echo "- ✅ Platform binaries created" >> $GITHUB_STEP_SUMMARY
fi
if [[ "${{ needs.docker.result }}" == "success" ]]; then
echo "- ✅ Docker image: \`ghcr.io/metaneutrons/knxmonitor:${{ needs.calculate-version.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY
elif [[ "${{ needs.docker.result }}" == "failure" ]]; then
echo "- ❌ Docker image build failed" >> $GITHUB_STEP_SUMMARY
fi
if [[ "${{ needs.update-homebrew.result }}" == "success" ]]; then
echo "- ✅ Homebrew formula updated automatically" >> $GITHUB_STEP_SUMMARY
elif [[ "${{ needs.update-homebrew.result }}" == "failure" ]]; then
echo "- ❌ Homebrew formula update failed (check artifacts for manual instructions)" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 🔗 Links" >> $GITHUB_STEP_SUMMARY
if [[ "${{ needs.release.result }}" == "success" ]]; then
echo "- [Release Page](https://github.com/${{ github.repository }}/releases/tag/${{ needs.calculate-version.outputs.tag }})" >> $GITHUB_STEP_SUMMARY
fi
if [[ "${{ needs.docker.result }}" == "success" ]]; then
echo "- [Docker Image](https://github.com/${{ github.repository }}/pkgs/container/knxmonitor)" >> $GITHUB_STEP_SUMMARY
fi
echo "- [Homebrew Tap](https://github.com/metaneutrons/homebrew-tap)" >> $GITHUB_STEP_SUMMARY
# Set overall workflow status
if [[ "${{ needs.build.result }}" != "success" || "${{ needs.release.result }}" != "success" ]]; then
echo "❌ Release failed - critical components failed" >> $GITHUB_STEP_SUMMARY
exit 1
elif [[ "${{ needs.docker.result }}" == "failure" && "${{ inputs.create_docker }}" == "true" ]]; then
echo "⚠️ Release completed with warnings - Docker build failed" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "✅ Release completed successfully" >> $GITHUB_STEP_SUMMARY
fi