Files
tor-guard-relay/.github/workflows/release.yml

622 lines
26 KiB
YAML

name: 🚀✨
on:
workflow_dispatch:
inputs:
build_mode:
description: 'Build mode'
required: true
default: 'rebuild'
type: choice
options:
- rebuild
- version_bump
variants:
description: 'Which variants to build'
required: true
default: 'both'
type: choice
options:
- both
- latest
- edge
release_type:
description: 'Release type (only for version_bump mode)'
required: false
default: 'patch'
type: choice
options:
- major
- minor
- patch
workflow_run:
workflows: ['🗑️🧹']
types: [completed]
schedule:
- cron: '30 18 * * 0'
- cron: '0 12 */3 * *'
permissions:
contents: read
env:
GHCR_REGISTRY: ghcr.io
GHCR_IMAGE_NAME: ${{ github.repository_owner }}/onion-relay
DOCKERHUB_IMAGE_NAME: r3bo0tbx1/onion-relay
jobs:
determine-version:
name: 🏷️ Determine Version and Build Type
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
version: ${{ steps.version.outputs.version }}
build_type: ${{ steps.version.outputs.build_type }}
is_release: ${{ steps.version.outputs.is_release }}
build_date: ${{ steps.version.outputs.build_date }}
short_sha: ${{ steps.version.outputs.short_sha }}
build_variants: ${{ steps.version.outputs.build_variants }}
skip: ${{ steps.version.outputs.skip }}
steps:
- name: 📥 Checkout Repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: 🏷️ Fetch All Tags
run: git fetch --tags --force
- name: 🔍 Detect Version and Build Type
id: version
run: |
set -e
echo "🔍 Determining version context..."
BUILD_VARIANTS="both"
latest_semver_tag() {
git tag --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1 || echo "v1.0.0"
}
if [[ "${GITHUB_EVENT_NAME}" == "workflow_run" && "${{ github.event.workflow_run.conclusion }}" == "success" ]]; then
if [[ "${{ github.event.workflow_run.event }}" != "push" ]]; then
echo "⏭️ Cleanup was triggered by '${{ github.event.workflow_run.event }}', not a tag push — skipping"
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
TRIGGER_SHA="${{ github.event.workflow_run.head_sha }}"
VERSION=$(git tag --sort=-v:refname --points-at "${TRIGGER_SHA}" | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1 || true)
if [ -z "${VERSION}" ]; then
echo "⏭️ No semver tag on triggering commit — skipping"
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
VERSION="${VERSION#v}"
BUILD_TYPE="release"
IS_RELEASE="true"
BUILD_VARIANTS="both"
echo "skip=false" >> "$GITHUB_OUTPUT"
echo "🏷️ Release tag detected via cleanup completion: v${VERSION}"
elif [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
BUILD_MODE="${{ github.event.inputs.build_mode }}"
BUILD_VARIANTS="${{ github.event.inputs.variants }}"
LATEST_TAG=$(latest_semver_tag)
if [[ "${BUILD_MODE}" == "rebuild" ]]; then
VERSION="${LATEST_TAG#v}"
BUILD_TYPE="manual-rebuild"
IS_RELEASE="false"
echo "🔄 Manual rebuild of last release: ${VERSION} (with updated packages)"
echo " Variants: ${BUILD_VARIANTS}"
else
VERSION="${LATEST_TAG#v}-manual-${GITHUB_RUN_NUMBER}"
BUILD_TYPE="manual"
IS_RELEASE="false"
echo "👤 Manual build version: ${VERSION}"
echo " Variants: ${BUILD_VARIANTS}"
fi
elif [[ "${GITHUB_EVENT_NAME}" == "schedule" ]]; then
LATEST_TAG=$(latest_semver_tag)
VERSION="${LATEST_TAG#v}"
IS_RELEASE="false"
CURRENT_HOUR=$(date -u +%H)
if [[ "${CURRENT_HOUR}" == "18" ]]; then
BUILD_TYPE="weekly"
BUILD_VARIANTS="latest"
echo "📅 Weekly rebuild of last release: ${VERSION} (stable variant with updated packages)"
else
BUILD_TYPE="edge-rebuild"
BUILD_VARIANTS="edge"
echo "⚡ Edge-only rebuild of last release: ${VERSION} (edge variant with updated packages)"
fi
else
LATEST_TAG=$(latest_semver_tag)
VERSION="${LATEST_TAG#v}"
BUILD_TYPE="unknown"
IS_RELEASE="false"
BUILD_VARIANTS="both"
echo "⚠️ Unknown trigger: ${VERSION}"
fi
BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
SHORT_SHA=$(git rev-parse --short HEAD)
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "build_type=${BUILD_TYPE}" >> "$GITHUB_OUTPUT"
echo "is_release=${IS_RELEASE}" >> "$GITHUB_OUTPUT"
echo "build_date=${BUILD_DATE}" >> "$GITHUB_OUTPUT"
echo "short_sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT"
echo "build_variants=${BUILD_VARIANTS}" >> "$GITHUB_OUTPUT"
- name: 📋 Version Information
run: |
echo "📦 Build Info:"
echo " 🏷️ Version: ${{ steps.version.outputs.version }}"
echo " 📝 Build Type: ${{ steps.version.outputs.build_type }}"
echo " ✅ Release: ${{ steps.version.outputs.is_release }}"
echo " 📅 Date: ${{ steps.version.outputs.build_date }}"
echo " 🔑 SHA: ${{ steps.version.outputs.short_sha }}"
build-and-push:
name: 🏗️ Multi-Arch Build and Push (${{ matrix.variant.name }})
runs-on: ubuntu-latest
needs: determine-version
permissions:
contents: read
packages: write
if: |
needs.determine-version.outputs.skip != 'true' &&
(github.event_name != 'workflow_run' ||
github.event.workflow_run.conclusion == 'success')
strategy:
fail-fast: false
matrix:
variant:
- name: stable
dockerfile: Dockerfile
suffix: ""
is_latest: "true"
base: "Alpine 3.23.3"
push_dockerhub: "true"
- name: edge
dockerfile: Dockerfile.edge
suffix: "-edge"
is_latest: "false"
base: "Alpine edge"
push_dockerhub: "true"
steps:
- name: 📥 Checkout Repository
uses: actions/checkout@v6
- name: 🎯 Check if variant should be built
id: should_build
run: |
BUILD_VARIANTS="${{ needs.determine-version.outputs.build_variants }}"
VARIANT_NAME="${{ matrix.variant.name }}"
SHOULD_BUILD="false"
if [ "$BUILD_VARIANTS" = "both" ]; then
SHOULD_BUILD="true"
elif [ "$BUILD_VARIANTS" = "latest" ] && [ "$VARIANT_NAME" = "stable" ]; then
SHOULD_BUILD="true"
elif [ "$BUILD_VARIANTS" = "edge" ] && [ "$VARIANT_NAME" = "edge" ]; then
SHOULD_BUILD="true"
fi
echo "should_build=${SHOULD_BUILD}" >> "$GITHUB_OUTPUT"
echo "🔍 Variant: ${VARIANT_NAME}, Build Variants: ${BUILD_VARIANTS}, Should Build: ${SHOULD_BUILD}"
- name: 🎯 Verify Tools Directory
if: steps.should_build.outputs.should_build == 'true'
run: |
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📝 Pre-Build: Verifying Tools"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
if [ ! -d "tools" ]; then
echo "❌ tools/ directory not found"
exit 1
fi
TOOL_COUNT=0
for file in tools/*; do
[ -f "$file" ] || continue
filename=$(basename "$file")
if head -1 "$file" 2>/dev/null | grep -q "^#!/bin/sh"; then
echo "✅ $filename (busybox sh)"
TOOL_COUNT=$((TOOL_COUNT + 1))
fi
done
echo ""
if [ $TOOL_COUNT -lt 1 ]; then
echo "❌ Build blocked: No tools found in tools/"
exit 1
else
echo "🎉 Found $TOOL_COUNT diagnostic tools (busybox-only, no .sh extension)"
fi
- name: 🔧 Normalize scripts before build
if: steps.should_build.outputs.should_build == 'true'
run: |
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🔧 Normalizing Line Endings and Permissions"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
sudo apt-get update -qq && sudo apt-get install -y dos2unix
echo "📄 Processing main scripts..."
for script in docker-entrypoint.sh healthcheck.sh Dockerfile Dockerfile.edge; do
if [ -f "$script" ]; then
dos2unix "$script" 2>/dev/null || true
echo " ✅ Normalized: $script"
fi
done
echo ""
echo "📁 Processing tools/*..."
if [ -d "tools" ]; then
find tools -type f -exec dos2unix {} \; 2>/dev/null || true
TOOL_COUNT=$(find tools -type f | wc -l)
echo " ✅ Normalized: $TOOL_COUNT tool scripts"
fi
echo ""
echo "🔐 Setting execute permissions..."
chmod +x docker-entrypoint.sh healthcheck.sh 2>/dev/null || true
[ -d "tools" ] && chmod +x tools/* 2>/dev/null || true
echo " ✅ Permissions verified"
echo ""
echo "🎉 Normalization complete"
- name: 🐳 Login to Docker Hub
if: steps.should_build.outputs.should_build == 'true' && matrix.variant.push_dockerhub == 'true'
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: 📦 Login to GitHub Container Registry
if: steps.should_build.outputs.should_build == 'true'
uses: docker/login-action@v4
with:
registry: ${{ env.GHCR_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: 🖥️ Set up QEMU
if: steps.should_build.outputs.should_build == 'true'
uses: docker/setup-qemu-action@v4
with:
platforms: arm64,amd64
- name: 🔨 Set up Docker Buildx
if: steps.should_build.outputs.should_build == 'true'
uses: docker/setup-buildx-action@v4
- name: 🏷️ Generate Docker Tags
if: steps.should_build.outputs.should_build == 'true'
id: tags
run: |
VERSION="${{ needs.determine-version.outputs.version }}"
BUILD_TYPE="${{ needs.determine-version.outputs.build_type }}"
VARIANT_NAME="${{ matrix.variant.name }}"
SUFFIX="${{ matrix.variant.suffix }}"
IS_LATEST="${{ matrix.variant.is_latest }}"
PUSH_DOCKERHUB="${{ matrix.variant.push_dockerhub }}"
echo "🏷️ Generating tags for variant: ${VARIANT_NAME}"
echo " Version: ${VERSION}${SUFFIX}"
echo " Build Type: ${BUILD_TYPE}"
echo " Is Latest: ${IS_LATEST}"
echo " Push to Docker Hub: ${PUSH_DOCKERHUB}"
echo ""
TAGS=()
TAGS+=("${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}:${VERSION}${SUFFIX}")
if [ "$BUILD_TYPE" = "release" ]; then
if [ "$IS_LATEST" = "true" ]; then
TAGS+=("${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}:latest")
else
TAGS+=("${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}:edge")
fi
if [ "$PUSH_DOCKERHUB" = "true" ]; then
if [ "$IS_LATEST" = "true" ]; then
TAGS+=("${{ env.DOCKERHUB_IMAGE_NAME }}:${VERSION}")
TAGS+=("${{ env.DOCKERHUB_IMAGE_NAME }}:latest")
else
TAGS+=("${{ env.DOCKERHUB_IMAGE_NAME }}:edge")
fi
fi
elif [ "$BUILD_TYPE" = "weekly" ] || [ "$BUILD_TYPE" = "manual-rebuild" ] || [ "$BUILD_TYPE" = "edge-rebuild" ]; then
if [ "$IS_LATEST" = "true" ]; then
TAGS+=("${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}:latest")
else
TAGS+=("${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}:edge")
fi
if [ "$PUSH_DOCKERHUB" = "true" ]; then
if [ "$IS_LATEST" = "true" ]; then
TAGS+=("${{ env.DOCKERHUB_IMAGE_NAME }}:${VERSION}")
TAGS+=("${{ env.DOCKERHUB_IMAGE_NAME }}:latest")
else
TAGS+=("${{ env.DOCKERHUB_IMAGE_NAME }}:edge")
fi
fi
else
if [ "$PUSH_DOCKERHUB" = "true" ]; then
if [ "$IS_LATEST" = "true" ]; then
TAGS+=("${{ env.DOCKERHUB_IMAGE_NAME }}:${VERSION}")
else
TAGS+=("${{ env.DOCKERHUB_IMAGE_NAME }}:edge")
fi
fi
fi
TAGS_STR=$(IFS=','; echo "${TAGS[*]}")
echo "tags=${TAGS_STR}" >> "$GITHUB_OUTPUT"
echo "📋 Generated tags:"
for tag in "${TAGS[@]}"; do
echo " - ${tag}"
done
- name: 🚀 Build and Push Multi-Arch Image
if: steps.should_build.outputs.should_build == 'true'
uses: docker/build-push-action@v7
with:
context: .
file: ./${{ matrix.variant.dockerfile }}
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.tags.outputs.tags }}
build-args: |
BUILD_DATE=${{ needs.determine-version.outputs.build_date }}
BUILD_VERSION=${{ needs.determine-version.outputs.version }}
cache-from: type=gha,scope=${{ matrix.variant.name }}
cache-to: type=gha,mode=max,scope=${{ matrix.variant.name }}
labels: |
org.opencontainers.image.title=Tor Guard Relay (${{ matrix.variant.name }})
org.opencontainers.image.description=Hardened Tor Guard Relay (${{ matrix.variant.base }})
org.opencontainers.image.version=${{ needs.determine-version.outputs.version }}${{ matrix.variant.suffix }}
org.opencontainers.image.created=${{ needs.determine-version.outputs.build_date }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
sbom: true
provenance: true
- name: 📋 Generate SBOM (CycloneDX & SPDX)
if: steps.should_build.outputs.should_build == 'true' && needs.determine-version.outputs.is_release == 'true'
run: |
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📋 Generating Software Bill of Materials (SBOM)"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
VERSION="${{ needs.determine-version.outputs.version }}"
SUFFIX="${{ matrix.variant.suffix }}"
VARIANT="${{ matrix.variant.name }}"
IMAGE="${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}:${VERSION}${SUFFIX}"
echo "📦 Generating SBOM for ${VARIANT} variant"
echo " Image: ${IMAGE}"
echo ""
echo "📄 Generating CycloneDX JSON format..."
syft "${IMAGE}" -o cyclonedx-json > "sbom-${VARIANT}-cyclonedx-v${VERSION}.json"
echo " ✅ sbom-${VARIANT}-cyclonedx-v${VERSION}.json"
echo "📄 Generating CycloneDX XML format..."
syft "${IMAGE}" -o cyclonedx-xml > "sbom-${VARIANT}-cyclonedx-v${VERSION}.xml"
echo " ✅ sbom-${VARIANT}-cyclonedx-v${VERSION}.xml"
echo "📄 Generating SPDX JSON format..."
syft "${IMAGE}" -o spdx-json > "sbom-${VARIANT}-spdx-v${VERSION}.json"
echo " ✅ sbom-${VARIANT}-spdx-v${VERSION}.json"
echo "📄 Generating SPDX tag-value format..."
syft "${IMAGE}" -o spdx-tag-value > "sbom-${VARIANT}-spdx-v${VERSION}.spdx"
echo " ✅ sbom-${VARIANT}-spdx-v${VERSION}.spdx"
echo "📄 Generating human-readable table..."
syft "${IMAGE}" -o table > "sbom-${VARIANT}-table-v${VERSION}.txt"
echo " ✅ sbom-${VARIANT}-table-v${VERSION}.txt"
echo ""
echo "✅ SBOM generation complete for ${VARIANT} variant"
echo ""
echo "📊 Package Statistics:"
jq '.components | length' "sbom-${VARIANT}-cyclonedx-v${VERSION}.json" | xargs echo " Total packages:"
- name: 📤 Upload SBOM Artifacts
if: steps.should_build.outputs.should_build == 'true' && needs.determine-version.outputs.is_release == 'true'
uses: actions/upload-artifact@v7
with:
name: sbom-${{ matrix.variant.name }}-v${{ needs.determine-version.outputs.version }}
path: |
sbom-${{ matrix.variant.name }}-*.json
sbom-${{ matrix.variant.name }}-*.xml
sbom-${{ matrix.variant.name }}-*.spdx
sbom-${{ matrix.variant.name }}-*.txt
retention-days: 7
release-notes:
name: 📝 Generate Release Notes
runs-on: ubuntu-latest
needs: [determine-version, build-and-push]
permissions:
contents: write
if: needs.determine-version.outputs.is_release == 'true'
steps:
- name: 📥 Checkout Repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: 🏷️ Fetch All Tags
run: git fetch --tags --force
- name: 📝 Generate Notes
run: |
VERSION="${{ needs.determine-version.outputs.version }}"
GHCR_IMAGE="${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}"
DOCKERHUB_IMAGE="${{ env.DOCKERHUB_IMAGE_NAME }}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📝 Generating Release Notes for v${VERSION}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
CHANGELOG_FOUND=0
if [ -f CHANGELOG.md ]; then
echo "🔍 Checking CHANGELOG.md for v${VERSION}..."
awk -v version="${VERSION}" '
$0 ~ "^##[[:space:]]*(\\[v?" version "\\]|v" version ")([[:space:]]*-.*)?$" {p=1; next}
p && /^##[[:space:]]*\[/ && !($0 ~ version) {p=0}
p
' CHANGELOG.md > tmp_notes.txt
if [ -s tmp_notes.txt ]; then
while [ -s tmp_notes.txt ]; do
LAST_LINE=$(tail -1 tmp_notes.txt)
case "$(printf '%s' "$LAST_LINE" | tr -d '[:space:]')" in
""|"---") sed -i '$ d' tmp_notes.txt ;;
*) break ;;
esac
done
echo "✅ Found changelog section for v${VERSION} in CHANGELOG.md"
CHANGELOG_FOUND=1
cat tmp_notes.txt > release_notes.md
else
echo "⚠️ No changelog section found for v${VERSION} in CHANGELOG.md"
fi
else
echo "⚠️ CHANGELOG.md not found"
fi
if [ "$CHANGELOG_FOUND" = "0" ]; then
echo "📋 Auto-generating release notes from commits..."
if [ -x scripts/release/generate-release-notes.sh ]; then
chmod +x scripts/release/generate-release-notes.sh
./scripts/release/generate-release-notes.sh --format github "${VERSION}" > tmp_auto_notes.md
sed '/^## 🧅/d; /^\*\*Full Changelog\*\*/d' tmp_auto_notes.md > release_notes.md
while [ -s release_notes.md ]; do
LAST_LINE=$(tail -1 release_notes.md)
case "$(printf '%s' "$LAST_LINE" | tr -d '[:space:]')" in
""|"---") sed -i '$ d' release_notes.md ;;
*) break ;;
esac
done
echo "✅ Auto-generated release notes from conventional commits"
else
echo "### Changes" > release_notes.md
echo "" >> release_notes.md
PREV_TAG=$(git tag --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | grep -v "^v${VERSION}$" | head -1)
[ -z "$PREV_TAG" ] && PREV_TAG=$(git rev-list --max-parents=0 HEAD)
git log --pretty=format:"- %s (\`%h\`) by %an" "${PREV_TAG}..HEAD" >> release_notes.md || echo "- Initial release" >> release_notes.md
echo "" >> release_notes.md
echo "⚠️ **Note:** Release notes were auto-generated from commit history." >> release_notes.md
echo "For detailed changes, see the commit history below." >> release_notes.md
echo "✅ Generated basic release notes from commit history"
fi
fi
echo "" >> release_notes.md
echo "---" >> release_notes.md
ALPINE_VERSION=$(grep -oP '^FROM alpine:\K[0-9.]+' Dockerfile 2>/dev/null || echo "latest")
echo "" >> release_notes.md
echo "### 🐳 Docker Images" >> release_notes.md
echo "" >> release_notes.md
echo "**Stable Variant (Recommended for Production)**" >> release_notes.md
echo "" >> release_notes.md
echo "- Base: Alpine ${ALPINE_VERSION}" >> release_notes.md
echo "- Proven stability with weekly security updates" >> release_notes.md
echo "" >> release_notes.md
echo "\`\`\`bash" >> release_notes.md
echo "# From GitHub Container Registry (GHCR)" >> release_notes.md
echo "docker pull ${GHCR_IMAGE}:${VERSION}" >> release_notes.md
echo "docker pull ${GHCR_IMAGE}:latest" >> release_notes.md
echo "" >> release_notes.md
echo "# From Docker Hub" >> release_notes.md
echo "docker pull ${DOCKERHUB_IMAGE}:${VERSION}" >> release_notes.md
echo "docker pull ${DOCKERHUB_IMAGE}:latest" >> release_notes.md
echo "\`\`\`" >> release_notes.md
echo "" >> release_notes.md
echo "**Edge Variant (Testing Only)**" >> release_notes.md
echo "" >> release_notes.md
echo "- Base: Alpine edge (bleeding edge)" >> release_notes.md
echo "- ⚠️ **NOT recommended for production** - faster updates, less stable" >> release_notes.md
echo "" >> release_notes.md
echo "\`\`\`bash" >> release_notes.md
echo "# From GitHub Container Registry (GHCR) - versioned + simple tags" >> release_notes.md
echo "docker pull ${GHCR_IMAGE}:${VERSION}-edge" >> release_notes.md
echo "docker pull ${GHCR_IMAGE}:edge" >> release_notes.md
echo "" >> release_notes.md
echo "# From Docker Hub - simple tag only" >> release_notes.md
echo "docker pull ${DOCKERHUB_IMAGE}:edge" >> release_notes.md
echo "\`\`\`" >> release_notes.md
echo "" >> release_notes.md
echo "### 📋 Software Bill of Materials (SBOM)" >> release_notes.md
echo "" >> release_notes.md
echo "This release includes comprehensive SBOM files for **both variants** for supply chain security:" >> release_notes.md
echo "" >> release_notes.md
echo "- **CycloneDX**: JSON and XML formats (stable + edge)" >> release_notes.md
echo "- **SPDX**: JSON and tag-value formats (stable + edge)" >> release_notes.md
echo "- **Human-readable**: Table format (stable + edge)" >> release_notes.md
echo "" >> release_notes.md
echo "Download SBOM files from the release assets below (prefixed with \`stable-\` or \`edge-\`)." >> release_notes.md
echo "" >> release_notes.md
echo "---" >> release_notes.md
echo "" >> release_notes.md
PREV_TAG=$(git tag --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | grep -v "^v${VERSION}$" | head -1)
[ -z "$PREV_TAG" ] && PREV_TAG="v1.0.0"
echo "**Full Changelog**: https://github.com/${{ github.repository }}/compare/${PREV_TAG}...v${VERSION}" >> release_notes.md
echo ""
echo "✅ Release notes generation complete"
- name: 📦 Download SBOM Artifacts (Stable)
uses: actions/download-artifact@v8
with:
name: sbom-stable-v${{ needs.determine-version.outputs.version }}
path: ./sbom
- name: 📦 Download SBOM Artifacts (Edge)
uses: actions/download-artifact@v8
with:
name: sbom-edge-v${{ needs.determine-version.outputs.version }}
path: ./sbom
- name: 🏷️ Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ needs.determine-version.outputs.version }}
name: "🧅 Tor Guard Relay v${{ needs.determine-version.outputs.version }}"
body_path: release_notes.md
files: |
sbom/*.json
sbom/*.xml
sbom/*.spdx
sbom/*.txt
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}