name: Build fleetctl macOS package # This workflow builds a signed and notarized macOS .pkg installer for fleetctl # for manual testing purposes only. # # NOTE: For production releases, the goreleaser workflow (.github/workflows/goreleaser-fleet.yaml) # handles building and uploading the fleetctl package automatically when a fleet-* tag is pushed. # This workflow is kept for manual testing only. # # TESTING: # To test the package build process: # 1. Go to Actions → Build fleetctl macOS package → Run workflow # 2. Select your branch (e.g., main or feature branch) # 3. Test mode defaults to true (recommended) - package will be built and uploaded as an artifact # 4. To test release upload, set test_mode to false (use with caution) on: workflow_dispatch: # Manual trigger for testing only inputs: test_mode: description: "Test mode - will skip release upload if enabled (recommended: true)" type: boolean default: true # This allows a subsequently queued workflow run to interrupt previous runs concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true defaults: run: # fail-fast using bash -eo pipefail. See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference shell: bash permissions: contents: write # Needed to upload release assets id-token: write # Needed for attestations attestations: write # Needed to create build provenance attestations jobs: build-fleetctl-pkg: runs-on: macos-latest timeout-minutes: 120 steps: - name: Harden Runner uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: audit - name: Checkout uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: fetch-depth: 0 persist-credentials: false - name: Extract version id: extract_version run: | REF_NAME="${{ github.ref_name }}" # Check if running from a tag (for testing tag-based workflows) if [[ "${{ github.ref }}" == refs/tags/* ]] && [[ "$REF_NAME" == fleet-* ]]; then # Extract version from tag (e.g., fleet-v4.10.0 -> 4.10.0) VERSION="${REF_NAME#fleet-v}" TAG_NAME="$REF_NAME" else # For branch-based manual triggers, use a test version VERSION="test-$(date +%Y%m%d-%H%M%S)" TAG_NAME="fleet-${VERSION}" fi # Determine test mode based on input (defaults to true for safety) if [ "${{ github.event.inputs.test_mode }}" = "false" ]; then IS_TEST_MODE="false" else # Default to test mode (true) for safety IS_TEST_MODE="true" fi echo "version=$VERSION" >> $GITHUB_OUTPUT echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT echo "is_test_mode=$IS_TEST_MODE" >> $GITHUB_OUTPUT echo "Fleet version: $VERSION" if [ "$IS_TEST_MODE" = "true" ]; then echo "⚠️ TEST MODE: Package will be built but NOT uploaded to release" else echo "⚠️ PRODUCTION MODE: Package will be uploaded to release $TAG_NAME" fi - name: Set up Node uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 with: node-version-file: package.json check-latest: true - name: Set up Go uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 with: go-version-file: "go.mod" - name: Install dependencies run: make deps - name: Generate code run: make generate-go - name: Build fleetctl binary run: | VERSION="${{ steps.extract_version.outputs.version }}" # Extract branch or tag name from ref if [[ "${{ github.ref }}" == refs/tags/* ]]; then BRANCH_NAME="${{ steps.extract_version.outputs.tag_name }}" else BRANCH_NAME="${GITHUB_REF#refs/heads/}" fi # Set up version ldflags LDFLAGS="-X github.com/fleetdm/fleet/v4/server/version.appName=fleetctl \ -X github.com/fleetdm/fleet/v4/server/version.version=${VERSION} \ -X github.com/fleetdm/fleet/v4/server/version.branch=${BRANCH_NAME} \ -X github.com/fleetdm/fleet/v4/server/version.revision=${GITHUB_SHA} \ -X github.com/fleetdm/fleet/v4/server/version.buildDate=$(date -u +%Y-%m-%d) \ -X github.com/fleetdm/fleet/v4/server/version.buildUser=github-actions" # Build for amd64 echo "Building fleetctl for darwin/amd64..." CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build \ -trimpath \ -ldflags "$LDFLAGS" \ -o fleetctl_amd64 \ ./cmd/fleetctl # Build for arm64 echo "Building fleetctl for darwin/arm64..." CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build \ -trimpath \ -ldflags "$LDFLAGS" \ -o fleetctl_arm64 \ ./cmd/fleetctl # Create universal binary using lipo echo "Creating universal binary..." lipo -create fleetctl_amd64 fleetctl_arm64 -output fleetctl chmod +x fleetctl # Clean up architecture-specific binaries rm -f fleetctl_amd64 fleetctl_arm64 # Verify we have the binary if [ ! -f fleetctl ]; then echo "Error: fleetctl binary not found after build" exit 1 fi # Verify binary works and show version ./fleetctl --version - name: Import application signing keys (for binary) id: import_app_cert env: APPLE_APPLICATION_CERTIFICATE: ${{ secrets.APPLE_APPLICATION_CERTIFICATE }} APPLE_APPLICATION_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_APPLICATION_CERTIFICATE_PASSWORD }} KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} run: | echo "$APPLE_APPLICATION_CERTIFICATE" | base64 --decode > app_certificate.p12 security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain || true security default-keychain -s build.keychain security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain security import app_certificate.p12 -k build.keychain -P "$APPLE_APPLICATION_CERTIFICATE_PASSWORD" -T /usr/bin/codesign security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain security find-identity -vv rm app_certificate.p12 # Extract the signing identity SHA1 from the imported certificate IDENTITY=$(security find-identity -v build.keychain | grep "Developer ID Application" | head -1 | awk '{print $2}') if [[ -z "$IDENTITY" ]]; then echo "Error: No Developer ID Application identity found in keychain" exit 1 fi echo "codesign_identity=$IDENTITY" >> "$GITHUB_OUTPUT" - name: Sign fleetctl binary run: | # Sign the binary with Developer ID Application certificate codesign --sign "${{ steps.import_app_cert.outputs.codesign_identity }}" \ --options runtime \ --timestamp \ --force \ --verbose \ fleetctl # Verify the signature codesign --verify --verbose fleetctl codesign --display --verbose fleetctl - name: Build, sign, and notarize .pkg env: APPLE_INSTALLER_CERTIFICATE: ${{ secrets.APPLE_INSTALLER_CERTIFICATE }} APPLE_INSTALLER_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_INSTALLER_CERTIFICATE_PASSWORD }} APPLE_USERNAME: ${{ secrets.APPLE_USERNAME }} APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_REPOSITORY: ${{ github.repository }} GITHUB_REF: ${{ github.ref }} SKIP_UPLOAD: ${{ steps.extract_version.outputs.is_test_mode }} run: | chmod +x tools/build-fleetctl-pkg/main.sh ./tools/build-fleetctl-pkg/main.sh ./fleetctl "${{ steps.extract_version.outputs.version }}" - name: Upload package artifact (test mode only) if: steps.extract_version.outputs.is_test_mode == 'true' uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: fleetctl-test-package path: dist/fleetctl_v${{ steps.extract_version.outputs.version }}_mac.pkg retention-days: 7 - name: Attest package uses: actions/attest-build-provenance@619dbb2e03e0189af0c55118e7d3c5e129e99726 # v2.0.0 with: subject-path: dist/fleetctl_v${{ steps.extract_version.outputs.version }}_mac.pkg push-to-registry: false