name: Release on: workflow_dispatch: inputs: release_type: description: 'Release type (only used for manual trigger)' required: true default: 'auto' type: choice options: - auto - patch - minor - major permissions: contents: write issues: write pull-requests: write jobs: image_matrix: name: Validate Docker image matrix runs-on: ubuntu-latest permissions: contents: read outputs: all: ${{ steps.matrix.outputs.all }} steps: - name: Checkout uses: actions/checkout@v7.0.1 - name: Test Docker image matrix run: bash docker/tests/image-matrix_test.sh - name: Export Docker image matrix id: matrix run: echo "all=$(jq -c . docker/image-matrix.json)" >> "$GITHUB_OUTPUT" validate_images: name: Validate Docker image (${{ matrix.image }}) needs: image_matrix runs-on: ubuntu-latest timeout-minutes: 45 permissions: contents: read strategy: fail-fast: false matrix: include: ${{ fromJSON(needs.image_matrix.outputs.all) }} steps: - name: Checkout uses: actions/checkout@v7.0.1 with: ref: ${{ github.sha }} - name: Populate frontend version information if: matrix.frontend == true run: cd webapp/frontend && ./git.version.sh - name: Set up QEMU uses: docker/setup-qemu-action@v4 with: platforms: ${{ matrix.qemu }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - name: Build image uses: docker/build-push-action@v7 with: context: . file: ${{ matrix.dockerfile }} platforms: ${{ matrix.platforms }} push: false tags: scrutiny-release-validation-${{ matrix.image }} cache-from: type=gha,scope=release-validation-${{ matrix.image }} cache-to: type=gha,mode=max,scope=release-validation-${{ matrix.image }} release: name: Semantic Release needs: validate_images runs-on: ubuntu-latest outputs: new_release_published: ${{ steps.semantic.outputs.new_release_published }} new_release_version: ${{ steps.semantic.outputs.new_release_version }} steps: - name: Checkout uses: actions/checkout@v7.0.1 with: fetch-depth: 0 persist-credentials: false - name: Setup Node.js uses: actions/setup-node@v7 with: node-version: 24.15.0 - name: Install release dependencies run: npm ci --ignore-scripts - name: Force release commit (manual trigger only) if: github.event_name == 'workflow_dispatch' && inputs.release_type != 'auto' env: GITHUB_TOKEN: ${{ secrets.SCRUTINY_GITHUB_TOKEN }} run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" case "${{ inputs.release_type }}" in patch) COMMIT_MSG="fix: manual release trigger" ;; minor) COMMIT_MSG="feat: manual release trigger" ;; major) COMMIT_MSG="feat!: manual release trigger (BREAKING CHANGE)" ;; esac git commit --allow-empty -m "$COMMIT_MSG" remote="https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git" git push "$remote" "HEAD:${{ github.ref_name }}" - name: Semantic Release id: semantic run: node .github/scripts/run-semantic-release.mjs env: GITHUB_TOKEN: ${{ secrets.SCRUTINY_GITHUB_TOKEN }} # semantic-release updates webapp/backend/pkg/version/version.go in the # release commit. The build job must compile the tagged release commit, # not the pre-release workspace state, so runtime banners and API # version surfaces stay on the semantic version. - name: Get previous tag id: prev_tag if: steps.semantic.outputs.new_release_published == 'true' run: | PREV_TAG=$(git describe --tags --abbrev=0 "v${{ steps.semantic.outputs.new_release_version }}^" 2>/dev/null || echo "") echo "tag=$PREV_TAG" >> "$GITHUB_OUTPUT" - name: Generate release notes from PRs id: release_notes if: steps.semantic.outputs.new_release_published == 'true' env: GITHUB_TOKEN: ${{ secrets.SCRUTINY_GITHUB_TOKEN }} run: | set -euo pipefail chmod +x .github/scripts/generate-release-notes.sh if .github/scripts/generate-release-notes.sh "${{ steps.prev_tag.outputs.tag }}" "v${{ steps.semantic.outputs.new_release_version }}" > /tmp/release-notes-raw.md; then cp /tmp/release-notes-raw.md /tmp/release-notes.md else echo "Release note generation failed; release notes need manual repair." >&2 exit 1 fi - name: Update release notes if: steps.semantic.outputs.new_release_published == 'true' env: GITHUB_TOKEN: ${{ secrets.SCRUTINY_GITHUB_TOKEN }} run: | if [ -f /tmp/release-notes.md ] && [ -s /tmp/release-notes.md ]; then gh release edit "v${{ steps.semantic.outputs.new_release_version }}" \ --repo ${{ github.repository }} \ --notes-file /tmp/release-notes.md echo "Release notes updated successfully" else echo "No release notes generated, keeping default" fi build: name: Build Binaries needs: release if: needs.release.outputs.new_release_published == 'true' runs-on: ${{ matrix.cfg.on }} env: STATIC: true strategy: fail-fast: false matrix: cfg: - { on: ubuntu-latest, goos: linux, goarch: amd64 } - { on: ubuntu-latest, goos: linux, goarch: arm, goarm: 5 } - { on: ubuntu-latest, goos: linux, goarch: arm, goarm: 6 } - { on: ubuntu-latest, goos: linux, goarch: arm, goarm: 7 } - { on: ubuntu-latest, goos: linux, goarch: arm64 } - { on: macos-latest, goos: darwin, goarch: amd64 } - { on: macos-latest, goos: darwin, goarch: arm64 } - { on: macos-latest, goos: freebsd, goarch: amd64 } - { on: windows-latest, goos: windows, goarch: amd64 } - { on: windows-latest, goos: windows, goarch: arm64 } steps: - name: Checkout uses: actions/checkout@v7.0.1 with: ref: v${{ needs.release.outputs.new_release_version }} # Build from the published release tag so the binaries embed the same # VERSION constant that semantic-release committed for that release. - name: Setup Go uses: actions/setup-go@v7 with: go-version-file: go.mod - name: Build Binaries env: GOOS: ${{ matrix.cfg.goos }} GOARCH: ${{ matrix.cfg.goarch }} GOARM: ${{ matrix.cfg.goarm }} run: make binary-clean binary-all - name: Package Collector Omnibus env: GOOS: ${{ matrix.cfg.goos }} GOARCH: ${{ matrix.cfg.goarch }} GOARM: ${{ matrix.cfg.goarm }} run: make package-collector-omnibus - name: Upload artifacts uses: actions/upload-artifact@v7.0.1 with: name: binaries-${{ matrix.cfg.goos }}-${{ matrix.cfg.goarch }}${{ matrix.cfg.goarm }} path: | scrutiny-web-* scrutiny-collector-metrics-* scrutiny-collector-mdadm-* scrutiny-collector-zfs-* scrutiny-collector-btrfs-* scrutiny-collector-performance-* scrutiny-collector-filesystem-* scrutiny-collector-omnibus-*.tar.gz scrutiny-collector-omnibus-*.zip upload-assets: name: Upload Release Assets needs: [release, build] if: needs.release.outputs.new_release_published == 'true' runs-on: ubuntu-latest steps: - name: Download all artifacts uses: actions/download-artifact@v8 with: pattern: binaries-* merge-multiple: true - name: List files run: ls -la - name: Generate checksums run: | sha256sum scrutiny-* > SHA256SUMS.txt echo "Generated checksums:" cat SHA256SUMS.txt - name: Upload to release env: GITHUB_TOKEN: ${{ secrets.SCRUTINY_GITHUB_TOKEN }} run: | VERSION="v${{ needs.release.outputs.new_release_version }}" for file in scrutiny-*; do if [ -f "$file" ]; then echo "Uploading $file..." gh release upload "$VERSION" "$file" --repo ${{ github.repository }} --clobber fi done echo "Uploading SHA256SUMS.txt..." gh release upload "$VERSION" SHA256SUMS.txt --repo ${{ github.repository }} --clobber