Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 126 additions & 12 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,22 +90,39 @@ jobs:

- run: pnpm install --frozen-lockfile

# Import Apple certificate for macOS signing
# Import Apple certificate for macOS signing (only when Developer ID is available)
- name: Import Apple certificate
if: matrix.os == 'macos-latest' || matrix.os == 'macos-15'
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
if [ -n "$APPLE_CERTIFICATE" ]; then
echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
rm certificate.p12
if [ -z "$APPLE_CERTIFICATE" ] || [ -z "$KEYCHAIN_PASSWORD" ]; then
echo "No Apple certificate configured — skipping import"
exit 0
fi
echo "$APPLE_CERTIFICATE" | base64 --decode > certificate.p12
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
rm certificate.p12

# Determine macOS signing identity: Developer ID if available, ad-hoc (-) otherwise
- name: Resolve macOS signing identity
if: matrix.os == 'macos-latest' || matrix.os == 'macos-15'
id: macos-signing
env:
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
run: |
if [ -n "$APPLE_SIGNING_IDENTITY" ]; then
echo "identity=$APPLE_SIGNING_IDENTITY" >> "$GITHUB_OUTPUT"
echo "Using Developer ID signing"
else
echo "identity=-" >> "$GITHUB_OUTPUT"
echo "Using ad-hoc signing (no Apple Developer ID)"
fi

- name: Build Tauri app
Expand All @@ -115,11 +132,11 @@ jobs:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
PKG_CONFIG_PATH: /usr/lib/x86_64-linux-gnu/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig
# macOS signing
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
# macOS signing — falls back to ad-hoc (-) when no Apple Developer cert
APPLE_SIGNING_IDENTITY: ${{ steps.macos-signing.outputs.identity }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
# macOS notarization
# macOS notarization (only works with Developer ID)
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
Expand All @@ -132,3 +149,100 @@ jobs:
releaseDraft: false
prerelease: false
updaterJsonKeepUniversal: true

# ─────────────────────────────────────────────────────────────
# Update Homebrew Tap: Push new version to homebrew-mcpmux
# ─────────────────────────────────────────────────────────────
update-homebrew:
needs: [release-please, build-release]
if: needs.release-please.outputs.release_created == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Wait for release assets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${{ needs.release-please.outputs.version }}"
TAG="${{ needs.release-please.outputs.tag_name }}"
echo "Checking release assets for $TAG..."

# Wait up to 5 minutes for macOS DMGs to appear
for i in $(seq 1 30); do
ASSETS=$(gh api repos/${{ github.repository }}/releases/tags/$TAG --jq '.assets[].name' 2>/dev/null || echo "")
if echo "$ASSETS" | grep -q "aarch64.dmg" && echo "$ASSETS" | grep -q "x64.dmg"; then
echo "Both macOS DMGs found"
break
fi
echo "Waiting for macOS DMGs... (attempt $i/30)"
sleep 10
done

- name: Compute SHA256 and update cask
env:
GH_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
run: |
VERSION="${{ needs.release-please.outputs.version }}"
BASE_URL="https://github.com/${{ github.repository }}/releases/download/v${VERSION}"

# Download DMGs and compute SHA256
echo "Downloading macOS DMGs..."
curl -fSL "${BASE_URL}/McpMux_${VERSION}_aarch64.dmg" -o arm64.dmg || { echo "ARM64 DMG not found — skipping Homebrew update"; exit 0; }
curl -fSL "${BASE_URL}/McpMux_${VERSION}_x64.dmg" -o x64.dmg || { echo "x64 DMG not found — skipping Homebrew update"; exit 0; }

SHA_ARM64=$(shasum -a 256 arm64.dmg | cut -d' ' -f1)
SHA_X64=$(shasum -a 256 x64.dmg | cut -d' ' -f1)
echo "ARM64 SHA256: $SHA_ARM64"
echo "x64 SHA256: $SHA_X64"

# Clone the tap repo and update the cask
git clone https://x-access-token:${GH_TOKEN}@github.com/ion-ash/homebrew-mcpmux.git tap

# Generate cask file (Ruby heredoc content with shell variable expansion)
CASK_FILE="tap/Casks/mcpmux.rb"
{
echo 'cask "mcpmux" do'
echo ' arch arm: "aarch64", intel: "x64"'
echo ''
echo " version \"${VERSION}\""
echo " sha256 arm: \"${SHA_ARM64}\","
echo " intel: \"${SHA_X64}\""
echo ''
echo ' url "https://github.com/ion-ash/mcp-mux/releases/download/v#{version}/McpMux_#{version}_#{arch}.dmg",'
echo ' verified: "github.com/ion-ash/mcp-mux/"'
echo ''
echo ' name "McpMux"'
echo ' desc "Unified MCP gateway and manager for AI clients"'
echo ' homepage "https://mcpmux.com"'
echo ''
echo ' depends_on macos: ">= :high_sierra"'
echo ''
echo ' livecheck do'
echo ' url "https://github.com/ion-ash/mcp-mux/releases/latest"'
echo ' strategy :github_latest'
echo ' end'
echo ''
echo ' app "McpMux.app"'
echo ''
echo ' # Remove quarantine for ad-hoc signed app (no Apple Developer ID)'
echo ' postflight do'
echo ' system_command "/usr/bin/xattr",'
echo ' args: ["-cr", "#{appdir}/McpMux.app"]'
echo ' end'
echo ''
echo ' zap trash: ['
echo ' "~/Library/Application Support/com.mcpmux.desktop",'
echo ' "~/Library/Preferences/com.mcpmux.desktop.plist",'
echo ' "~/Library/Caches/com.mcpmux.desktop",'
echo ' "~/Library/Saved Application State/com.mcpmux.desktop.savedState",'
echo ' ]'
echo 'end'
} > "$CASK_FILE"

cd tap
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add Casks/mcpmux.rb
git commit -m "Update mcpmux to ${VERSION}"
git push
116 changes: 114 additions & 2 deletions scripts/setup-release.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,127 @@
# Run once to generate signing keys for Tauri auto-updater.
#
# Usage:
# .\setup-release.ps1 # Generate new signing key
# .\setup-release.ps1 # Generate new Tauri updater signing key
# .\setup-release.ps1 -ShowPubkey # Show public key for tauri.conf.json
# .\setup-release.ps1 -SetupHomebrew # Set up Homebrew tap token in GitHub
# .\setup-release.ps1 -ShowSecrets # Show all required GitHub secrets and their status

param(
[switch]$ShowPubkey
[switch]$ShowPubkey,
[switch]$SetupHomebrew,
[switch]$ShowSecrets
)

$keyPath = "$env:USERPROFILE\.tauri\mcpmux.key"
$pubkeyPath = "$env:USERPROFILE\.tauri\mcpmux.key.pub"
$repo = "ion-ash/mcp-mux"
$tapRepo = "ion-ash/homebrew-mcpmux"

# ── Show all secrets status ──────────────────────────────────
if ($ShowSecrets) {
Write-Host ""
Write-Host "McpMux GitHub Secrets Status" -ForegroundColor Cyan
Write-Host "============================" -ForegroundColor Cyan
Write-Host ""
Write-Host "Repository: https://github.com/$repo/settings/secrets/actions" -ForegroundColor Gray
Write-Host ""

# Check which secrets exist via gh CLI
$existingSecrets = @()
try {
$existingSecrets = gh secret list --repo $repo --json name --jq '.[].name' 2>$null | ForEach-Object { $_.Trim() }
} catch {}

$allSecrets = @(
@{ Name = "TAURI_SIGNING_PRIVATE_KEY"; Category = "Updater (required)"; Note = "Generated by: .\setup-release.ps1" }
@{ Name = "TAURI_SIGNING_PRIVATE_KEY_PASSWORD"; Category = "Updater (optional)"; Note = "Password for the signing key" }
@{ Name = "HOMEBREW_TAP_TOKEN"; Category = "Homebrew (required)"; Note = "Generated by: .\setup-release.ps1 -SetupHomebrew" }
@{ Name = "APPLE_SIGNING_IDENTITY"; Category = "macOS (optional)"; Note = "Leave empty for ad-hoc signing (-)" }
@{ Name = "APPLE_CERTIFICATE"; Category = "macOS (optional)"; Note = "Base64 .p12 — only with Apple Developer ID" }
@{ Name = "APPLE_CERTIFICATE_PASSWORD"; Category = "macOS (optional)"; Note = ".p12 password" }
@{ Name = "APPLE_ID"; Category = "macOS notarize (optional)"; Note = "Apple ID email" }
@{ Name = "APPLE_PASSWORD"; Category = "macOS notarize (optional)"; Note = "App-specific password" }
@{ Name = "APPLE_TEAM_ID"; Category = "macOS notarize (optional)"; Note = "10-char team ID" }
@{ Name = "KEYCHAIN_PASSWORD"; Category = "macOS (optional)"; Note = "Temp keychain password for CI" }
)

foreach ($secret in $allSecrets) {
$status = if ($existingSecrets -contains $secret.Name) { "SET" } else { "NOT SET" }
$color = if ($status -eq "SET") { "Green" } else {
if ($secret.Category -match "optional") { "DarkGray" } else { "Yellow" }
}
$icon = if ($status -eq "SET") { "[OK]" } else { "[ ]" }
Write-Host " $icon " -NoNewline -ForegroundColor $color
Write-Host "$($secret.Name)" -NoNewline -ForegroundColor White
Write-Host " — $($secret.Note)" -ForegroundColor DarkGray
}

Write-Host ""
Write-Host "macOS ad-hoc signing:" -ForegroundColor Cyan
Write-Host " No secrets needed! The release workflow defaults to ad-hoc signing (-)" -ForegroundColor Gray
Write-Host " when APPLE_SIGNING_IDENTITY is not set. This is sufficient for" -ForegroundColor Gray
Write-Host " distribution via Homebrew tap (postflight removes quarantine)." -ForegroundColor Gray
Write-Host ""
exit 0
}

# ── Setup Homebrew tap token ─────────────────────────────────
if ($SetupHomebrew) {
Write-Host ""
Write-Host "Homebrew Tap Token Setup" -ForegroundColor Cyan
Write-Host "========================" -ForegroundColor Cyan
Write-Host ""
Write-Host "This creates a fine-grained GitHub token for the release workflow" -ForegroundColor Gray
Write-Host "to push cask updates to $tapRepo." -ForegroundColor Gray
Write-Host ""

# Check if gh is available
if (-not (Get-Command gh -ErrorAction SilentlyContinue)) {
Write-Host "GitHub CLI (gh) is required. Install: https://cli.github.com" -ForegroundColor Red
exit 1
}

# Check auth
$authStatus = gh auth status 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Host "Not authenticated with GitHub CLI. Run: gh auth login" -ForegroundColor Red
exit 1
}

Write-Host "Creating fine-grained PAT for $tapRepo..." -ForegroundColor Gray
Write-Host ""
Write-Host "Since 'gh' cannot create fine-grained tokens via CLI yet," -ForegroundColor Yellow
Write-Host "please create one manually:" -ForegroundColor Yellow
Write-Host ""
Write-Host " 1. Go to: https://github.com/settings/personal-access-tokens/new" -ForegroundColor White
Write-Host " 2. Token name: mcpmux-homebrew-tap" -ForegroundColor White
Write-Host " 3. Expiration: 90 days (or custom)" -ForegroundColor White
Write-Host " 4. Repository: $tapRepo (only)" -ForegroundColor White
Write-Host " 5. Permissions: Contents — Read and write" -ForegroundColor White
Write-Host " 6. Click 'Generate token' and copy it" -ForegroundColor White
Write-Host ""

$token = Read-Host "Paste the token here (or press Enter to skip)"
if ([string]::IsNullOrWhiteSpace($token)) {
Write-Host "Skipped. Set it manually:" -ForegroundColor Yellow
Write-Host " gh secret set HOMEBREW_TAP_TOKEN --repo $repo" -ForegroundColor White
Write-Host ""
exit 0
}

# Set the secret
Write-Host ""
Write-Host "Setting HOMEBREW_TAP_TOKEN secret on $repo..." -ForegroundColor Gray
$token | gh secret set HOMEBREW_TAP_TOKEN --repo $repo
if ($LASTEXITCODE -eq 0) {
Write-Host "Done! HOMEBREW_TAP_TOKEN is now set." -ForegroundColor Green
} else {
Write-Host "Failed to set secret. Try manually:" -ForegroundColor Red
Write-Host " gh secret set HOMEBREW_TAP_TOKEN --repo $repo" -ForegroundColor White
}
Write-Host ""
exit 0
}

if ($ShowPubkey) {
if (Test-Path $pubkeyPath) {
Expand Down