name: Build Experimental Release Binaries
on:
workflow_dispatch:
inputs:
version:
description: Version to publish (e.g., 1.2.1-experimental.1)
required: true
type: string
features:
description: Cargo features (comma-separated, e.g., apps)
required: false
type: string
default: ""
env:
VERSION: ${{ inputs.version }}
jobs:
validate:
name: Validate version format
runs-on: ubuntu-24.04
steps:
- name: Validate experimental version pattern
run: |
if ! echo "${{ inputs.version }}" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+-experimental\.[0-9]+$'; then
echo "Error: Version must match pattern: X.Y.Z-experimental.N"
echo "Examples: 1.2.1-experimental.1, 2.0.0-experimental.3"
echo "Provided: ${{ inputs.version }}"
exit 1
fi
update-version:
name: Update version in install scripts
needs: validate
runs-on: ubuntu-24.04
permissions:
contents: write
outputs:
release_commit: ${{ steps.commit-version.outputs.commit_sha }}
temp_branch: ${{ steps.commit-version.outputs.temp_branch }}
steps:
- uses: actions/checkout@v5
with:
ref: ${{ github.ref }}
- name: Update install script versions
run: |
sed -i 's/^PACKAGE_VERSION=".*"$/PACKAGE_VERSION="v${{ inputs.version }}"/' scripts/nix/install.sh
sed -i "s/^\$package_version = '.*'$/\$package_version = 'v${{ inputs.version }}'/" scripts/windows/install.ps1
- name: Commit and push to temporary branch
id: commit-version
run: |
TEMP_BRANCH="release-temp-${{ inputs.version }}-${{ github.run_id }}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git checkout -b "$TEMP_BRANCH"
git add scripts/nix/install.sh scripts/windows/install.ps1
git commit -m "chore: update install scripts to version ${{ inputs.version }}"
git push origin "$TEMP_BRANCH"
echo "commit_sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
echo "temp_branch=$TEMP_BRANCH" >> $GITHUB_OUTPUT
build:
name: Release binaries
needs: update-version
strategy:
matrix:
include:
# Linux compiles itself
- os: ubuntu-24.04
bundle: linux
targets: cross-aarch64-unknown-linux-gnu cross-aarch64-unknown-linux-musl cross-x86_64-unknown-linux-gnu cross-x86_64-unknown-linux-musl
# We can compile the windows target from linux
- os: ubuntu-24.04
bundle: windows
targets: cross-aarch64-pc-windows-gnullvm cross-x86_64-pc-windows-gnullvm
# Apple SDK does not allow us to cross compile from non-apple-branded
# machines, so we run that bundle on a macOS runner
- os: macos-latest
bundle: darwin
targets: cross-aarch64-apple-darwin cross-x86_64-apple-darwin
runs-on: ${{ matrix.os }}
permissions:
contents: write
packages: write
attestations: write
id-token: write
steps:
- uses: actions/checkout@v5
with:
ref: ${{ needs.update-version.outputs.release_commit }}
- uses: nixbuild/nix-quick-install-action@v30
with:
nix_conf: ${{ env.nix_conf }}
- name: Restore and save Nix store
uses: nix-community/cache-nix-action@v6
with:
primary-key: release-${{ matrix.bundle }}-${{ hashFiles('Cargo.lock', '**/Cargo.toml', 'flake.nix', 'flake.lock', 'rust-toolchain.toml') }}
restore-prefixes-first-match: |
release-${{ matrix.bundle }}-
build-${{ runner.os }}-
purge: true
purge-prefixes: release-${{ matrix.bundle }}-
purge-created: 0
purge-primary-key: never
gc-max-store-size: 5G
- name: Build binaries
env:
APOLLO_MCP_BUILD_FEATURES: ${{ inputs.features }}
run: |
mkdir release
for BUILD_TARGET in ${{ matrix.targets }}; do
TARGET=${BUILD_TARGET#"cross-"}
echo "Scaffolding release for $TARGET..."
mkdir -p "release/$TARGET/dist"
cp README.md LICENSE "release/$TARGET/dist"
echo "Building release for $TARGET..."
nix build --impure .#$BUILD_TARGET
cp result/bin/* "release/$TARGET/dist/"
done
- name: Sign Apple Binary
if: ${{ runner.os == 'macOS' }}
env:
MACOS_CERT_BUNDLE_PASSWORD: ${{ secrets.MACOS_CERT_BUNDLE_PASSWORD }}
MACOS_CERT_BUNDLE_BASE64: ${{ secrets.MACOS_CERT_BUNDLE_BASE64 }}
MACOS_KEYCHAIN_PASSWORD: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_USERNAME: ${{ secrets.APPLE_USERNAME }}
KEYCHAIN_NAME: "apollo-mcp-server-keychain"
ENTITLEMENTS_PATH: "macos-entitlements.plist"
run: |
echo "Pre-check: Valid Codesigning Identify"
security find-identity -v -p codesigning
echo "Pre-check: Codesigning Identify"
security find-identity -p codesigning
echo "Pre-check: Any Identify"
security find-identity
echo "|||||||||||||||||||||||||||||||||||||||||||||"
# Create a temporary keychain
EPHEMERAL_KEYCHAIN=`mktemp -d`
echo "Creating keychain..."
security create-keychain -p "${MACOS_KEYCHAIN_PASSWORD}" $KEYCHAIN_NAME
echo "Removing relock timeout on keychain..."
security set-keychain-settings $KEYCHAIN_NAME
echo "Decoding certificate bundle..."
echo "${MACOS_CERT_BUNDLE_BASE64}" | base64 --decode > $EPHEMERAL_KEYCHAIN/certificate.p12
echo "Importing codesigning certificate to build keychain..."
security import $EPHEMERAL_KEYCHAIN/certificate.p12 -k $KEYCHAIN_NAME -P "${MACOS_CERT_BUNDLE_PASSWORD}" -T /usr/bin/codesign
echo "Adding the codesign tool to the security partition-list..."
security set-key-partition-list -S "apple-tool:,apple:,codesign:" -s -k "${MACOS_KEYCHAIN_PASSWORD}" $KEYCHAIN_NAME
echo "Setting default keychain..."
security default-keychain -d user -s $KEYCHAIN_NAME
echo "Unlocking keychain..."
security unlock-keychain -p "${MACOS_KEYCHAIN_PASSWORD}" $KEYCHAIN_NAME
echo "Verifying keychain is set up correctly..."
security find-identity -v -p codesigning
echo "|||||||||||||||||||||||||||||||||||||||||||||"
echo "Post-check: Valid Codesigning Identify"
security find-identity -v -p codesigning
echo "Post-check: Codesigning Identify"
security find-identity -p codesigning
echo "Post-check: Any Identify"
security find-identity
echo "|||||||||||||||||||||||||||||||||||||||||||||"
# Sign each binary
for RELEASE in release/*/; do
RELEASE=${RELEASE%/}
RELEASE=${RELEASE#"release/"}
BINARY_PATH="release/$RELEASE/dist/apollo-mcp-server"
echo "Starting code signing for $RELEASE..."
echo "> Signing code (step 1)..."
codesign --sign "$APPLE_TEAM_ID" --options runtime --entitlements $ENTITLEMENTS_PATH --force --timestamp "$BINARY_PATH" -v
echo "> Signing code (step 2)..."
codesign -vvv --deep --strict "$BINARY_PATH"
echo "> Zipping dist..."
TMP_DIST=`mktemp -d`
mkdir $TMP_DIST/dist
cp "$BINARY_PATH" "$TMP_DIST/dist/"
zip -r "$TMP_DIST/apollo-mcp-server-v$VERSION.zip" "$TMP_DIST/dist"
echo "> Beginning notarization process (might take up to 20m)..."
xcrun notarytool submit "$TMP_DIST/apollo-mcp-server-v$VERSION.zip" \
--apple-id "$APPLE_USERNAME" \
--password "$APPLE_NOTARIZATION_PASSWORD" \
--team-id "$APPLE_TEAM_ID" \
--wait \
--timeout 20m
echo "> Cleaning up release..."
rm -rf $TMP_DIST
done
echo "Cleaning up ephemeral keychain..."
rm -rf $EPHEMERAL_KEYCHAIN/
- name: Create release bundles
run: |
mkdir artifacts
for RELEASE in release/*/; do
# Remove trailing slash and leading parent
RELEASE=${RELEASE%/}
RELEASE=${RELEASE#"release/"}
RENAMED=${RELEASE/x86_64-pc-windows-gnullvm/x86_64-pc-windows-msvc}
RENAMED=${RENAMED/aarch64-pc-windows-gnullvm/aarch64-pc-windows-msvc}
echo "Creating an artifact for $RELEASE"
tar -C release/$RELEASE -cf - dist/ | gzip -9 > artifacts/apollo-mcp-server-v$VERSION-$RENAMED.tar.gz
done
# We only need to generate the config schema for a release once, so we do it
# on the linux host since it is the cheapest.
- name: Generate config schema
if: ${{ matrix.bundle == 'linux' }}
run: |
./release/x86_64-unknown-linux-musl/dist/config-schema > artifacts/config.schema.json
- name: Upload release artifacts
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ env.VERSION }}
target_commitish: ${{ needs.update-version.outputs.release_commit }}
files: artifacts/*
prerelease: ${{ contains(env.VERSION, '-experimental.') }}
make_latest: false
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v2
with:
subject-path: "artifacts/*"
publish:
name: Publish the release
needs: [build, update-version]
runs-on: ubuntu-24.04
steps:
- name: Make latest
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ env.VERSION }}
target_commitish: ${{ needs.update-version.outputs.release_commit }}
prerelease: ${{ contains(env.VERSION, '-experimental.') }}
cleanup:
name: Cleanup temporary branch
needs: [publish, update-version]
runs-on: ubuntu-24.04
permissions:
contents: write
if: always()
steps:
- uses: actions/checkout@v5
- name: Delete temporary branch
run: |
git push origin --delete ${{ needs.update-version.outputs.temp_branch }} || true