name: Release Build
on:
push:
tags:
- 'v*' # Trigger on version tags like v0.1.0
permissions:
contents: write # Needed to create releases
packages: write # Needed to push to GitHub Container Registry
jobs:
validate_version:
name: Validate Version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate tag format
run: |
if [[ ! "${GITHUB_REF}" =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "❌ Invalid tag format. Must be v#.#.#"
exit 1
fi
echo "✅ Tag format is valid"
- name: Check version in Cargo.toml
run: |
TAG_VERSION=${GITHUB_REF#refs/tags/v}
echo "Tag version: $TAG_VERSION"
CARGO_VERSION=$(grep '^version' Cargo.toml | head -1 | cut -d '"' -f 2)
echo "Cargo.toml version: $CARGO_VERSION"
if [ "$CARGO_VERSION" != "$TAG_VERSION" ]; then
echo "❌ Version mismatch"
echo " Tag version: $TAG_VERSION"
echo " Cargo.toml version: $CARGO_VERSION"
exit 1
fi
echo "✅ Version matches: $TAG_VERSION"
build_binaries:
name: Build Binaries for ${{ matrix.target }}
needs: validate_version
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
# Linux Intel (musl for static linking)
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
asset_name_suffix: linux-amd64
output_name: mcp-server-wazuh
# Windows Intel
- os: windows-latest
target: x86_64-pc-windows-msvc
asset_name_suffix: windows-amd64.exe
output_name: mcp-server-wazuh.exe
# macOS Intel
- os: macos-latest
target: x86_64-apple-darwin
asset_name_suffix: macos-amd64
output_name: mcp-server-wazuh
# macOS Apple Silicon
- os: macos-14
target: aarch64-apple-darwin
asset_name_suffix: macos-arm64
output_name: mcp-server-wazuh
steps:
- uses: actions/checkout@v4
- name: Set up Rust for target ${{ matrix.target }}
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Install musl-tools (Linux MUSL target only)
if: matrix.os == 'ubuntu-latest' && contains(matrix.target, 'musl')
run: |
sudo apt-get update -y
sudo apt-get install -y musl-tools
- name: Build binary
run: cargo build --verbose --release --target ${{ matrix.target }}
- name: Check dynamic dependencies (macOS only)
if: matrix.os == 'macos-latest' || matrix.os == 'macos-14'
run: |
echo "=== Checking dynamic library dependencies ==="
otool -L ./target/${{ matrix.target }}/release/${{ matrix.output_name }} || true
echo ""
echo "=== Checking for problematic dependencies ==="
if otool -L ./target/${{ matrix.target }}/release/${{ matrix.output_name }} | grep -E "(liblzma|/opt/homebrew|/usr/local)"; then
echo "WARNING: Found dynamic dependencies that may cause issues with code signing"
fi
- name: Import Apple Certificate (macOS only)
if: matrix.os == 'macos-latest' || matrix.os == 'macos-14'
env:
APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: |
# Debug: Check if secrets are set (without revealing them)
echo "=== Checking secrets ==="
if [ -z "$APPLE_CERTIFICATE_BASE64" ]; then
echo "ERROR: APPLE_CERTIFICATE_BASE64 is empty!"
exit 1
else
echo "APPLE_CERTIFICATE_BASE64: Set (length: ${#APPLE_CERTIFICATE_BASE64})"
fi
if [ -z "$APPLE_CERTIFICATE_PASSWORD" ]; then
echo "WARNING: APPLE_CERTIFICATE_PASSWORD is empty"
else
echo "APPLE_CERTIFICATE_PASSWORD: Set (length: ${#APPLE_CERTIFICATE_PASSWORD})"
fi
# Create temporary keychain with proper extension
security create-keychain -p temp-password build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p temp-password build.keychain
security set-keychain-settings -lut 21600 build.keychain
# Add build keychain to search list
security list-keychains -d user -s build.keychain $(security list-keychains -d user | sed s/\"//g)
# Import certificate with -A flag to avoid access control issues
echo "$APPLE_CERTIFICATE_BASE64" | base64 --decode > certificate.p12
# Debug: Check p12 file
echo "=== Checking .p12 file ==="
ls -la certificate.p12
file certificate.p12
# Import certificate (should contain both cert and private key)
echo "=== Importing certificate ==="
security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -A -T /usr/bin/codesign
IMPORT_RESULT=$?
echo "Import exit code: $IMPORT_RESULT"
# Debug: List all items in keychain
echo "=== All certificates in build.keychain ==="
security find-certificate -a build.keychain || true
echo "=== All identities (including non-codesigning) ==="
security find-identity -v build.keychain || true
# Import Apple intermediate certificate (DER format)
curl -o DeveloperIDG2CA.cer https://www.apple.com/certificateauthority/DeveloperIDG2CA.cer
security import DeveloperIDG2CA.cer -k build.keychain -A -T /usr/bin/codesign
# Import Apple Worldwide Developer Relations CA G3 (DER format)
curl -o AppleWWDRCAG3.cer https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer
security import AppleWWDRCAG3.cer -k build.keychain -A -T /usr/bin/codesign
# Set partition list to avoid password prompts
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k temp-password build.keychain
# Clean up certificate files
rm certificate.p12 DeveloperIDG2CA.cer AppleWWDRCAG3.cer
- name: Code Sign Binary (macOS only)
if: matrix.os == 'macos-latest' || matrix.os == 'macos-14'
env:
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
run: |
# Check identities in build keychain
echo "=== Code signing identities in build.keychain ==="
security find-identity -v -p codesigning build.keychain || true
# Extract signing identity hash from build keychain
SIGNING_HASH=$(security find-identity -v -p codesigning build.keychain | grep "$APPLE_SIGNING_IDENTITY" | grep -oE "[0-9A-F]{40}" | head -n 1)
echo "Using signing hash: $SIGNING_HASH"
# Sign the binary using the SHA-1 hash
/usr/bin/codesign --force --sign "$SIGNING_HASH" --timestamp --options runtime ./target/${{ matrix.target }}/release/${{ matrix.output_name }} -v
# Verify signature
/usr/bin/codesign --verify --verbose ./target/${{ matrix.target }}/release/${{ matrix.output_name }}
- name: Notarize Binary (macOS only)
if: matrix.os == 'macos-latest' || matrix.os == 'macos-14'
env:
APPLE_API_KEY_BASE64: ${{ secrets.APPLE_API_KEY_BASE64 }}
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER_ID: ${{ secrets.APPLE_API_ISSUER_ID }}
run: |
# Create API key file
echo "=== Creating API key file ==="
API_KEY_FILE="AuthKey_${APPLE_API_KEY_ID}.p8"
echo "$APPLE_API_KEY_BASE64" | base64 --decode > "$API_KEY_FILE"
echo "Created: $API_KEY_FILE"
# Create zip file for notarization using ditto (preserves metadata better)
echo "=== Creating zip file for notarization ==="
ZIP_FILE="mcp-server-wazuh-${{ matrix.asset_name_suffix }}-notarization.zip"
ditto -c -k --sequesterRsrc --keepParent ./target/${{ matrix.target }}/release/${{ matrix.output_name }} "$ZIP_FILE"
echo "Created: $ZIP_FILE"
# Submit for notarization
echo "=== Submitting for notarization ==="
echo "This may take several minutes..."
xcrun notarytool submit "$ZIP_FILE" \
--key "$API_KEY_FILE" \
--key-id "$APPLE_API_KEY_ID" \
--issuer "$APPLE_API_ISSUER_ID" \
--wait
# Attempt to staple the notarization (will fail for command-line tools - this is expected)
echo "=== Attempting to staple notarization ==="
echo "Note: Stapling fails for command-line tools - this is normal"
xcrun stapler staple ./target/${{ matrix.target }}/release/${{ matrix.output_name }} || echo "Stapling failed (expected for command-line tools)"
# Final verification
echo "=== Final signature and notarization verification ==="
codesign --verify --verbose ./target/${{ matrix.target }}/release/${{ matrix.output_name }}
spctl --assess --type execute --verbose ./target/${{ matrix.target }}/release/${{ matrix.output_name }} || echo "spctl assessment completed"
# Clean up files
rm -f "$ZIP_FILE" "$API_KEY_FILE"
echo "=== Notarization completed successfully ==="
- name: Rename binary for upload
shell: bash
run: |
cp ./target/${{ matrix.target }}/release/${{ matrix.output_name }} mcp-server-wazuh-${{ matrix.asset_name_suffix }}
- name: Upload Release Asset
uses: softprops/action-gh-release@v2
with:
files: mcp-server-wazuh-${{ matrix.asset_name_suffix }}
build_docker:
name: Build and Push Docker Image
needs: validate_version
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in 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/gbrigandi/mcp-server-wazuh
tags: |
type=ref,event=tag
type=raw,value=latest
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
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