Skip to content

Build and Release (Matrix) #19

Build and Release (Matrix)

Build and Release (Matrix) #19

Workflow file for this run

name: Build and Release (Matrix)
on:
push:
tags:
- "**"
workflow_dispatch:
inputs:
release_version:
description: "Release version (e.g., v1.0.0 - leave empty for auto-increment)"
required: false
type: string
wildduck_version:
description: "Wildduck version (leave empty for latest)"
required: false
type: string
zonemta_version:
description: "Zone-MTA version (leave empty for latest)"
required: false
type: string
zonemta_template_version:
description: "Zone-MTA Template version (leave empty for latest)"
required: false
type: string
haraka_version:
description: "Haraka version (leave empty for latest)"
required: false
type: string
jobs:
determine-version:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
outputs:
version: ${{ steps.set_version.outputs.version }}
steps:
- name: Determine release version
id: set_version
run: |
if [ -n "${{ inputs.release_version }}" ]; then
# Use provided version
VERSION="${{ inputs.release_version }}"
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
https://api.github.com/repos/${{ github.repository }}/git/refs/tags/${VERSION})
if [ "$STATUS" = "200" ]; then
echo "❌ Error: Tag $VERSION already exists"
exit 1
fi
echo "Using manual version: $VERSION"
echo "version=$VERSION" >> $GITHUB_OUTPUT
else
# Get latest release and increment patch version
LATEST_RELEASE=$(curl -s https://api.github.com/repos/${{ github.repository }}/releases/latest \
| jq -r '.tag_name')
if [ -z "$LATEST_RELEASE" ] || [ "$LATEST_RELEASE" = "null" ]; then
# No releases yet, start with v1.0.0-dispatch
NEW_VERSION="v1.0.0-dispatch"
else
# Remove 'v' prefix and any existing suffix
BASE_VERSION=$(echo "$LATEST_RELEASE" | sed 's/^v//' | sed 's/-dispatch$//')
# Split version into components
IFS='.' read -r MAJOR MINOR PATCH <<< "$BASE_VERSION"
# Increment patch version
PATCH=$((PATCH + 1))
# Create new version with -dispatch suffix
NEW_VERSION="v${MAJOR}.${MINOR}.${PATCH}-dispatch"
fi
echo "Auto-generated version: $NEW_VERSION"
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
fi
build:
needs: [determine-version]
if: always() && (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && needs.determine-version.result == 'success'))
runs-on: ubuntu-latest
strategy:
matrix:
project:
- name: wildduck
repo: zone-eu/wildduck
ref: master
- name: zone-mta
repo: zone-eu/zone-mta
ref: master
- name: zone-mta-template
repo: zone-eu/zone-mta-template
ref: master
- name: haraka
repo: haraka/Haraka
ref: master
steps:
- name: Checkout workflow repo
uses: actions/checkout@v6
- name: Get version for ${{ matrix.project.name }}
id: get_version
run: |
# Check if explicit version is provided via workflow input
case "${{ matrix.project.name }}" in
wildduck)
EXPLICIT_VERSION="${{ inputs.wildduck_version }}"
;;
zone-mta)
EXPLICIT_VERSION="${{ inputs.zonemta_version }}"
;;
zone-mta-template)
EXPLICIT_VERSION="${{ inputs.zonemta_template_version }}"
;;
haraka)
EXPLICIT_VERSION="${{ inputs.haraka_version }}"
;;
esac
if [ -n "$EXPLICIT_VERSION" ]; then
echo "Using explicit version: $EXPLICIT_VERSION"
echo "tag=$EXPLICIT_VERSION" >> $GITHUB_OUTPUT
else
# Fetch latest tag
LATEST_TAG=$(
curl -s "https://api.github.com/repos/${{ matrix.project.repo }}/tags?per_page=100" |
jq -r '.[].name' |
grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' |
sort -V |
tail -n 1
)
if [ -z "$LATEST_TAG" ] || [ "$LATEST_TAG" = "null" ]; then
echo "No tags found, falling back to master"
LATEST_TAG="${{ matrix.project.ref }}"
fi
echo "tag=$LATEST_TAG" >> $GITHUB_OUTPUT
echo "Using tag/ref: $LATEST_TAG for ${{ matrix.project.name }}"
fi
- name: Checkout ${{ matrix.project.name }}
uses: actions/checkout@v6
with:
repository: ${{ matrix.project.repo }}
ref: ${{ steps.get_version.outputs.tag }}
path: ${{ matrix.project.name }}
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: "24"
- name: Install dependencies
run: npm i --omit=dev
working-directory: ${{ matrix.project.name }}
- name: Install plugins for ${{ matrix.project.name }}
run: |
PLUGIN_FILE="${GITHUB_WORKSPACE}/plugins/${{ matrix.project.name }}.txt"
if [ -f "$PLUGIN_FILE" ]; then
echo "📦 Installing plugins for ${{ matrix.project.name }}..."
plugin_count=0
while IFS= read -r plugin || [ -n "$plugin" ]; do
# Skip empty lines and comments
if [ -z "$plugin" ] || [[ "$plugin" =~ ^#.* ]]; then
continue
fi
# Trim whitespace
plugin=$(echo "$plugin" | xargs)
echo "Installing: $plugin"
if npm install --save-exact "$plugin"; then
plugin_count=$((plugin_count + 1))
echo "✓ Successfully installed $plugin"
else
echo "✗ Failed to install $plugin"
exit 1
fi
done < "$PLUGIN_FILE"
echo "✅ Installed $plugin_count plugins successfully"
else
echo "❌ No plugin file found, skipping plugin installation"
fi
working-directory: ${{ matrix.project.name }}
- name: Generate plugin summary for release notes
run: |
PLUGIN_FILE="${GITHUB_WORKSPACE}/plugins/${{ matrix.project.name }}.txt"
SUMMARY_FILE="${GITHUB_WORKSPACE}/${{ matrix.project.name }}-plugins-summary.txt"
: > "$SUMMARY_FILE"
if [ -f "$PLUGIN_FILE" ]; then
while IFS= read -r plugin || [ -n "$plugin" ]; do
if [ -z "$plugin" ] || [[ "$plugin" =~ ^#.* ]]; then
continue
fi
plugin=$(echo "$plugin" | xargs)
version=$(
PLUGIN_NAME="$plugin" \
node -p "const pkg=require('./package.json'); const deps={...(pkg.dependencies||{}), ...(pkg.optionalDependencies||{})}; deps[process.env.PLUGIN_NAME] || ''"
)
if [ -z "$version" ]; then
version="not-installed"
fi
printf "%s|%s\n" "$plugin" "$version" >> "$SUMMARY_FILE"
done < "$PLUGIN_FILE"
else
echo "__NO_PLUGIN_FILE__|n/a" > "$SUMMARY_FILE"
fi
if [ ! -s "$SUMMARY_FILE" ]; then
echo "__NO_PLUGINS__|n/a" > "$SUMMARY_FILE"
fi
working-directory: ${{ matrix.project.name }}
- name: Create archives
run: |
cd ${{ matrix.project.name }}
zip -q -r ../${{ matrix.project.name }}-${{ steps.get_version.outputs.tag }}.zip . -x "*.git" "*.git/*"
tar --exclude='.git' --exclude='.git/*' -czf ../${{ matrix.project.name }}-${{ steps.get_version.outputs.tag }}.tar.gz .
- name: Upload artifact
uses: actions/upload-artifact@v5
with:
name: ${{ matrix.project.name }}-build
path: |
${{ matrix.project.name }}-${{ steps.get_version.outputs.tag }}.zip
${{ matrix.project.name }}-${{ steps.get_version.outputs.tag }}.tar.gz
${{ matrix.project.name }}-plugins-summary.txt
create-release:
needs: [determine-version, build]
if: always() && needs.build.result == 'success'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
if: github.event_name == 'workflow_dispatch'
- name: Download all artifacts
uses: actions/download-artifact@v6
with:
path: artifacts
- name: Get release version
id: get_release_version
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "version=${{ needs.determine-version.outputs.version }}" >> $GITHUB_OUTPUT
else
echo "version=${{ github.ref_name }}" >> $GITHUB_OUTPUT
fi
- name: Create tag for workflow_dispatch
if: github.event_name == 'workflow_dispatch'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag ${{ steps.get_release_version.outputs.version }}
git push origin ${{ steps.get_release_version.outputs.version }}
- name: Generate release body
env:
RELEASE_TRIGGER: ${{ github.event_name == 'workflow_dispatch' && 'Manual Dispatch' || 'Tag Push' }}
run: |
cat > release-body.md <<EOF
## Mail Server Stack Build
**Trigger:** ${RELEASE_TRIGGER}
EOF
write_plugin_section() {
local app_name="$1"
local project_slug="$2"
local summary_file="artifacts/${project_slug}-build/${project_slug}-plugins-summary.txt"
{
echo ""
echo "### ${app_name}"
if [ ! -f "$summary_file" ]; then
echo "- Plugin summary not available."
return
fi
while IFS='|' read -r plugin version || [ -n "$plugin" ]; do
case "$plugin" in
"__NO_PLUGIN_FILE__")
echo "- No plugin file configured."
return
;;
"__NO_PLUGINS__")
echo "- No plugins installed."
return
;;
esac
echo "- \`$plugin\`: \`$version\`"
done < "$summary_file"
} >> release-body.md
}
write_plugin_section "Wildduck" "wildduck"
write_plugin_section "Zone-MTA" "zone-mta"
write_plugin_section "Zone-MTA Template" "zone-mta-template"
write_plugin_section "Haraka" "haraka"
echo "" >> release-body.md
echo "All projects built with production-ready plugins added." >> release-body.md
- name: Create Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.get_release_version.outputs.version }}
files: |
artifacts/*/*.zip
artifacts/*/*.tar.gz
draft: false
prerelease: false
generate_release_notes: false
body_path: release-body.md