From d5dcf15d669614eb9cbbc7c7e2f81932a26d24ea Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 14 Jun 2026 13:09:48 +0100 Subject: [PATCH] feat(ci): add release-binaries workflow for terraphim-ai release Refs #2706 - Matrix build agent/cli/grep with macOS sign and upload to terraphim-ai - Bump workspace to 1.20.5 and pin terraphim_service Gitea registry dep - Add sign-macos-binary.sh helper script --- .cargo/config.toml | 3 + .github/workflows/release-binaries.yml | 187 +++++++++++++++++++++++++ Cargo.toml | 5 +- crates/terraphim_agent/Cargo.toml | 2 +- crates/terraphim_cli/Cargo.toml | 2 +- crates/terraphim_grep/Cargo.toml | 2 +- scripts/sign-macos-binary.sh | 99 +++++++++++++ 7 files changed, 296 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/release-binaries.yml create mode 100755 scripts/sign-macos-binary.sh diff --git a/.cargo/config.toml b/.cargo/config.toml index 21c3d42..4e45242 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,3 +1,6 @@ [registry] global-credential-providers = ["cargo:token"] +[registries.terraphim] +index = "sparse+https://git.terraphim.cloud/api/packages/terraphim/cargo/" + diff --git a/.github/workflows/release-binaries.yml b/.github/workflows/release-binaries.yml new file mode 100644 index 0000000..d407a54 --- /dev/null +++ b/.github/workflows/release-binaries.yml @@ -0,0 +1,187 @@ +name: Release Client Binaries + +on: + workflow_dispatch: + inputs: + version: + description: 'Release version without v prefix (e.g. 1.20.5)' + required: true + type: string + release_tag: + description: 'GitHub release tag (e.g. v1.20.5)' + required: true + type: string + target_repo: + description: 'GitHub repo to attach binaries to' + required: false + default: terraphim-ai + type: string + +permissions: + contents: write + +env: + CARGO_TERM_COLOR: always + +jobs: + build-binaries: + name: Build client binaries for ${{ matrix.target }} + strategy: + fail-fast: false + matrix: + include: + - os: [self-hosted, bigbox] + target: x86_64-unknown-linux-gnu + use_cross: false + - os: [self-hosted, bigbox] + target: x86_64-unknown-linux-musl + use_cross: true + - os: [self-hosted, bigbox] + target: aarch64-unknown-linux-musl + use_cross: true + - os: [self-hosted, macOS] + target: x86_64-apple-darwin + use_cross: false + - os: [self-hosted, macOS] + target: aarch64-apple-darwin + use_cross: false + - os: windows-latest + target: x86_64-pc-windows-msvc + use_cross: false + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + - name: Install zig + if: contains(matrix.target, 'apple-darwin') || contains(matrix.target, 'windows') + shell: bash + run: | + if command -v zig &>/dev/null; then exit 0; fi + if command -v brew &>/dev/null; then brew install zig; fi + if command -v choco &>/dev/null; then choco install zig -y; fi + - name: Install cross + if: matrix.use_cross + run: cargo install cross + - uses: Swatinem/rust-cache@v2 + if: matrix.target != 'x86_64-unknown-linux-gnu' + with: + key: clients-${{ matrix.target }} + - name: Build client binaries + env: + CARGO_REGISTRIES_TERRAPHIM_TOKEN: ${{ secrets.CARGO_REGISTRIES_TERRAPHIM_TOKEN }} + run: | + BUILD="${{ matrix.use_cross && 'cross' || 'cargo' }}" + $BUILD build --release --target ${{ matrix.target }} -p terraphim_agent --bin terraphim-agent + $BUILD build --release --target ${{ matrix.target }} -p terraphim_cli --bin terraphim-cli + $BUILD build --release --target ${{ matrix.target }} -p terraphim_grep --bin terraphim-grep --features "code-search openrouter" + - name: Package artifacts (Unix) + if: matrix.os != 'windows-latest' + env: + VERSION: ${{ inputs.version }} + run: | + mkdir -p artifacts + tar -czf "artifacts/terraphim-agent-${VERSION}-${{ matrix.target }}.tar.gz" -C "target/${{ matrix.target }}/release" terraphim-agent + tar -czf "artifacts/terraphim-cli-${VERSION}-${{ matrix.target }}.tar.gz" -C "target/${{ matrix.target }}/release" terraphim-cli + tar -czf "artifacts/terraphim-grep-${VERSION}-${{ matrix.target }}.tar.gz" -C "target/${{ matrix.target }}/release" terraphim-grep + cp target/${{ matrix.target }}/release/terraphim-agent artifacts/terraphim-agent-${{ matrix.target }} + cp target/${{ matrix.target }}/release/terraphim-cli artifacts/terraphim-cli-${{ matrix.target }} + cp target/${{ matrix.target }}/release/terraphim-grep artifacts/terraphim-grep-${{ matrix.target }} + chmod +x artifacts/* + - name: Package artifacts (Windows) + if: matrix.os == 'windows-latest' + shell: bash + env: + VERSION: ${{ inputs.version }} + run: | + mkdir -p artifacts + cd "target/${{ matrix.target }}/release" + 7z a -tzip "../../../artifacts/terraphim-agent-${VERSION}-${{ matrix.target }}.zip" terraphim-agent.exe + 7z a -tzip "../../../artifacts/terraphim-cli-${VERSION}-${{ matrix.target }}.zip" terraphim-cli.exe + 7z a -tzip "../../../artifacts/terraphim-grep-${VERSION}-${{ matrix.target }}.zip" terraphim-grep.exe + cd - + cp target/${{ matrix.target }}/release/terraphim-agent.exe artifacts/terraphim-agent-${{ matrix.target }}.exe + cp target/${{ matrix.target }}/release/terraphim-cli.exe artifacts/terraphim-cli-${{ matrix.target }}.exe + cp target/${{ matrix.target }}/release/terraphim-grep.exe artifacts/terraphim-grep-${{ matrix.target }}.exe + - uses: actions/upload-artifact@v4 + with: + name: client-binaries-${{ matrix.target }} + path: artifacts/* + + create-universal-macos: + name: Create macOS universal client binaries + needs: build-binaries + if: always() && needs.build-binaries.result != 'cancelled' + runs-on: [self-hosted, macOS] + steps: + - uses: actions/download-artifact@v4 + with: + name: client-binaries-x86_64-apple-darwin + path: x86_64 + - uses: actions/download-artifact@v4 + with: + name: client-binaries-aarch64-apple-darwin + path: aarch64 + - run: | + mkdir -p universal + lipo -create x86_64/terraphim-agent-x86_64-apple-darwin aarch64/terraphim-agent-aarch64-apple-darwin -output universal/terraphim-agent-universal-apple-darwin + lipo -create x86_64/terraphim-grep-x86_64-apple-darwin aarch64/terraphim-grep-aarch64-apple-darwin -output universal/terraphim-grep-universal-apple-darwin + chmod +x universal/* + - uses: actions/upload-artifact@v4 + with: + name: client-binaries-universal-apple-darwin + path: universal/* + + sign-and-notarize-macos: + name: Sign and notarize macOS client binaries + needs: create-universal-macos + if: always() && needs.create-universal-macos.result == 'success' + runs-on: [self-hosted, macOS] + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + name: client-binaries-universal-apple-darwin + path: universal + - uses: 1password/install-cli-action@v2 + - name: Load signing credentials + env: + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + run: | + echo "APPLE_ID=$(op read 'op://TerraphimPlatform/apple.developer.credentials/username' --no-newline)" >> $GITHUB_ENV + echo "APPLE_TEAM_ID=$(op read 'op://TerraphimPlatform/apple.developer.credentials/APPLE_TEAM_ID' --no-newline)" >> $GITHUB_ENV + echo "APPLE_APP_PASSWORD=$(op read 'op://TerraphimPlatform/apple.developer.credentials/APPLE_APP_SPECIFIC_PASSWORD' --no-newline)" >> $GITHUB_ENV + echo "CERT_BASE64=$(op read 'op://TerraphimPlatform/apple.developer.certificate/base64' --no-newline)" >> $GITHUB_ENV + echo "CERT_PASSWORD=$(op read 'op://TerraphimPlatform/apple.developer.certificate/password' --no-newline)" >> $GITHUB_ENV + - name: Sign and notarize agent and grep + env: + RUNNER_TEMP: ${{ runner.temp }} + run: | + chmod +x scripts/sign-macos-binary.sh + ./scripts/sign-macos-binary.sh universal/terraphim-agent-universal-apple-darwin "$APPLE_ID" "$APPLE_TEAM_ID" "$APPLE_APP_PASSWORD" "$CERT_BASE64" "$CERT_PASSWORD" + ./scripts/sign-macos-binary.sh universal/terraphim-grep-universal-apple-darwin "$APPLE_ID" "$APPLE_TEAM_ID" "$APPLE_APP_PASSWORD" "$CERT_BASE64" "$CERT_PASSWORD" + - uses: actions/upload-artifact@v4 + with: + name: client-binaries-signed-universal-apple-darwin + path: universal/* + + upload-to-target-release: + name: Attach client binaries to terraphim-ai release + needs: [build-binaries, sign-and-notarize-macos] + if: always() && needs.build-binaries.result == 'success' + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v4 + with: + pattern: client-binaries* + path: release-assets + merge-multiple: true + - name: Upload to target GitHub release + env: + GH_TOKEN: ${{ secrets.TERRAPHIM_AI_RELEASE_TOKEN || secrets.GITHUB_TOKEN }} + run: | + TAG="${{ inputs.release_tag }}" + REPO="terraphim/${{ inputs.target_repo }}" + find release-assets -type f | sort + gh release upload "$TAG" release-assets/* --repo "$REPO" --clobber diff --git a/Cargo.toml b/Cargo.toml index 97fdaf3..f9065de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ members = [ ] [workspace.package] -version = "1.20.4" +version = "1.20.5" edition = "2024" authors = ["Terraphim Team "] documentation = "https://terraphim.ai" @@ -23,6 +23,9 @@ repository = "https://git.terraphim.cloud/terraphim/terraphim-clients" license = "Apache-2.0" readme = "README.md" +[patch.crates-io] +terraphim_service = { version = "1.20.5", registry = "terraphim" } + [workspace.dependencies] tokio = { version = "1.0", features = ["full"] } reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false } diff --git a/crates/terraphim_agent/Cargo.toml b/crates/terraphim_agent/Cargo.toml index f7bec7f..cebb720 100644 --- a/crates/terraphim_agent/Cargo.toml +++ b/crates/terraphim_agent/Cargo.toml @@ -74,7 +74,7 @@ terraphim_persistence = { version = "1.0.0" } terraphim_config = { version = "1.0.0" } terraphim_command_runtime = { path = "../terraphim_command_runtime", version = "0.1.0" } terraphim_automata = { version = "1.19.2" } -terraphim_service = { version = "1.0.0", default-features = false } +terraphim_service = { version = "1.20.5", default-features = false, registry = "terraphim" } terraphim_middleware = { version = "1.0.0" } terraphim_rolegraph = { version = "1.0.0" } terraphim_hooks = { path = "../terraphim_hooks", version = "1.0.0" } diff --git a/crates/terraphim_cli/Cargo.toml b/crates/terraphim_cli/Cargo.toml index c27c587..a992949 100644 --- a/crates/terraphim_cli/Cargo.toml +++ b/crates/terraphim_cli/Cargo.toml @@ -18,7 +18,7 @@ path = "src/main.rs" [dependencies] # Core terraphim crates -terraphim_service = { version = "1.0.0" } +terraphim_service = { version = "1.20.5", registry = "terraphim" } terraphim_config = { version = "1.0.0" } terraphim_command_runtime = { path = "../terraphim_command_runtime", version = "0.1.0" } terraphim_types = { version = "1.0.0" } diff --git a/crates/terraphim_grep/Cargo.toml b/crates/terraphim_grep/Cargo.toml index f3a95da..71e8571 100644 --- a/crates/terraphim_grep/Cargo.toml +++ b/crates/terraphim_grep/Cargo.toml @@ -28,7 +28,7 @@ log.workspace = true terraphim_types = { version = "1.15.0" } terraphim_rolegraph = { version = "1.15.0" } terraphim_automata = { version = "1.19.2" } -terraphim_service = { version = "1.16.15", optional = true } +terraphim_service = { version = "1.20.5", optional = true, registry = "terraphim" } terraphim_config = { version = "1.15.0" } fff-search = { version = "0.8.4", optional = true } diff --git a/scripts/sign-macos-binary.sh b/scripts/sign-macos-binary.sh new file mode 100755 index 0000000..af4bd7e --- /dev/null +++ b/scripts/sign-macos-binary.sh @@ -0,0 +1,99 @@ +#!/bin/bash +set -euo pipefail + +# Sign and notarize a macOS binary +# Usage: ./sign-macos-binary.sh + +# Parameters passed from workflow (not hardcoded secrets) +BINARY_PATH="$1" +APPLE_ID="$2" +TEAM_ID="$3" +APP_PASS="$4" +CERT_BASE64="$5" +CERT_PASS="$6" + +echo "==> Signing and notarizing: $(basename "$BINARY_PATH")" + +# Create temporary keychain +KEYCHAIN_PATH="$RUNNER_TEMP/signing.keychain-db" +KEYCHAIN_PASS=$(openssl rand -base64 32) + +echo "==> Creating temporary keychain" +security create-keychain -p "$KEYCHAIN_PASS" "$KEYCHAIN_PATH" +security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" +security unlock-keychain -p "$KEYCHAIN_PASS" "$KEYCHAIN_PATH" + +# Import certificate +echo "==> Importing certificate" +CERT_PATH="$RUNNER_TEMP/certificate.p12" +# Remove newlines from base64 before decoding (macOS base64 is strict) +echo "$CERT_BASE64" | tr -d '\n' | base64 --decode > "$CERT_PATH" + +security import "$CERT_PATH" \ + -k "$KEYCHAIN_PATH" \ + -P "$CERT_PASS" \ + -T /usr/bin/codesign \ + -T /usr/bin/security + +# Set key partition list to allow codesign to access the key +security set-key-partition-list \ + -S apple-tool:,apple: \ + -s -k "$KEYCHAIN_PASS" \ + "$KEYCHAIN_PATH" + +# Add keychain to search list +security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | sed s/\"//g) + +# Find signing identity +SIGNING_IDENTITY=$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" | grep "Developer ID Application" | head -1 | awk -F'"' '{print $2}') +echo "==> Found signing identity: $SIGNING_IDENTITY" + +# Sign the binary +echo "==> Signing binary" +codesign \ + --sign "$SIGNING_IDENTITY" \ + --options runtime \ + --timestamp \ + --verbose \ + "$BINARY_PATH" + +# Verify signature +echo "==> Verifying signature" +codesign --verify --deep --strict --verbose=2 "$BINARY_PATH" + +# Create ZIP for notarization +ZIP_PATH="${BINARY_PATH}.zip" +echo "==> Creating ZIP for notarization" +ditto -c -k --keepParent "$BINARY_PATH" "$ZIP_PATH" + +# Submit for notarization +echo "==> Submitting for notarization" +xcrun notarytool submit "$ZIP_PATH" \ + --apple-id "$APPLE_ID" \ + --team-id "$TEAM_ID" \ + --password "$APP_PASS" \ + --wait + +# Check notarization status +echo "==> Checking notarization status" +SUBMISSION_ID=$(xcrun notarytool history \ + --apple-id "$APPLE_ID" \ + --team-id "$TEAM_ID" \ + --password "$APP_PASS" \ + | grep -m1 "id:" | awk '{print $2}') + +xcrun notarytool log "$SUBMISSION_ID" \ + --apple-id "$APPLE_ID" \ + --team-id "$TEAM_ID" \ + --password "$APP_PASS" + +# Verify with spctl +echo "==> Verifying Gatekeeper acceptance" +spctl --assess --type execute --verbose "$BINARY_PATH" || true + +# Cleanup +echo "==> Cleaning up" +rm -f "$CERT_PATH" "$ZIP_PATH" +security delete-keychain "$KEYCHAIN_PATH" || true + +echo "✅ Successfully signed and notarized: $(basename "$BINARY_PATH")"