From 1a8e19deb8504f02abd665cdf63edf4f8723ffc9 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 8 Jun 2026 13:36:27 +1000 Subject: [PATCH 01/14] Add fastlane match scaffold for code signing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scaffolds the `set_up_signing` lane that fetches the Developer ID Application certificate via `fastlane match` (readonly) from Automattic's S3 bucket on the Buildkite signing agents. This is cert delivery only — the build/sign/notarize steps land separately. Part of AINFRA-2466. The `Gemfile` pins `multi_json` because the latest released fastlane (2.235.0) crashes at startup on Ruby 3.3+ without it; see the comment for the upstream cause and the fastlane fix that makes it removable. Generated by the apple:ruby-fastlane-setup Claude Code skill. --- Generated with the help of Claude Code, https://code.claude.com Co-Authored-By: Claude Opus 4.8 (1M context) --- Gemfile | 13 ++ Gemfile.lock | 322 ++++++++++++++++++++++++++++++++++++++++++++ fastlane/.gitignore | 2 + fastlane/Fastfile | 54 ++++++++ 4 files changed, 391 insertions(+) create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 fastlane/.gitignore create mode 100644 fastlane/Fastfile diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..61de0c9 --- /dev/null +++ b/Gemfile @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gem 'fastlane', '~> 2.233' +gem 'fastlane-plugin-wpmreleasetoolkit', '~> 14.4' +# fastlane <= 2.235.0 crashes at startup on Ruby 3.3+ with "multi_json is not +# part of the bundle": Google stopped pulling multi_json transitively +# (googleapis/google-api-ruby-client#26611) and fastlane eagerly loads its +# Google Play actions, which require it through representable. fastlane re-added +# it as a direct dependency for 2.236.0 (fastlane/fastlane#30062) — drop this +# line once the lock is on fastlane >= 2.236.0. +gem 'multi_json' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..cbc4576 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,322 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.8) + abbrev (0.1.2) + addressable (2.9.0) + public_suffix (>= 2.0.2, < 8.0) + artifactory (3.0.17) + atomos (0.1.3) + aws-eventstream (1.4.0) + aws-partitions (1.1258.0) + aws-sdk-core (3.251.0) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.992.0) + aws-sigv4 (~> 1.9) + base64 + bigdecimal + jmespath (~> 1, >= 1.6.1) + logger + aws-sdk-kms (1.129.0) + aws-sdk-core (~> 3, >= 3.248.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.225.0) + aws-sdk-core (~> 3, >= 3.248.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.12.1) + aws-eventstream (~> 1, >= 1.0.2) + babosa (1.0.4) + base64 (0.3.0) + benchmark (0.5.0) + bigdecimal (4.1.2) + buildkit (1.6.1) + sawyer (>= 0.6) + chroma (0.2.0) + claide (1.1.0) + colored (1.2) + colored2 (3.1.2) + commander (4.6.0) + highline (~> 2.0.0) + csv (3.3.5) + declarative (0.0.20) + diffy (3.4.4) + digest-crc (0.7.0) + rake (>= 12.0.0, < 14.0.0) + domain_name (0.6.20240107) + dotenv (2.8.1) + emoji_regex (3.2.3) + erubi (1.13.1) + excon (0.112.0) + faraday (1.10.5) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) + ruby2_keywords (>= 0.0.4) + faraday-cookie_jar (0.0.8) + faraday (>= 0.8.0) + http-cookie (>= 1.0.0) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.1) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-multipart (1.2.0) + multipart-post (~> 2.0) + faraday-net_http (1.0.2) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.4) + faraday_middleware (1.2.1) + faraday (~> 1.0) + fastimage (2.4.1) + fastlane (2.235.0) + CFPropertyList (>= 2.3, < 5.0.0) + abbrev (~> 0.1) + addressable (>= 2.8, < 3.0.0) + artifactory (~> 3.0) + aws-sdk-s3 (~> 1.197) + babosa (>= 1.0.3, < 2.0.0) + base64 (~> 0.2) + benchmark (>= 0.1.0) + bundler (>= 2.4.0, < 5.0.0) + colored (~> 1.2) + commander (~> 4.6) + csv (~> 3.3) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 4.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 1.0) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 1.0) + fastimage (>= 2.1.0, < 3.0.0) + fastlane-sirp (>= 1.1.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-apis-androidpublisher_v3 (~> 0.3) + google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-env (>= 1.6.0, < 2.3.0) + google-cloud-storage (~> 1.31) + highline (~> 2.0) + http-cookie (~> 1.0.5) + json (< 3.0.0) + jwt (>= 2.1.0, < 4) + logger (>= 1.6, < 2.0) + mini_magick (>= 4.9.4, < 5.0.0) + multipart-post (>= 2.0.0, < 3.0.0) + mutex_m (~> 0.3) + naturally (~> 2.2) + nkf (~> 0.2) + optparse (>= 0.1.1, < 1.0.0) + ostruct (>= 0.1.0) + plist (>= 3.1.0, < 4.0.0) + rubyzip (>= 2.0.0, < 3.0.0) + security (= 0.1.5) + simctl (~> 1.6.3) + terminal-notifier (>= 2.0.0, < 3.0.0) + terminal-table (~> 3) + tty-screen (>= 0.6.3, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) + xcpretty (~> 0.4.1) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) + fastlane-plugin-wpmreleasetoolkit (14.6.0) + buildkit (~> 1.5) + chroma (= 0.2.0) + diffy (~> 3.3) + dotenv (~> 2.8) + fastlane (~> 2.231) + gettext (~> 3.5) + git (~> 1.3) + google-cloud-storage (~> 1.31) + java-properties (~> 0.3.0) + nokogiri (~> 1.19, >= 1.19.3) + octokit (~> 6.1) + parallel (~> 1.14) + plist (~> 3.1) + progress_bar (~> 1.3) + rake (>= 12.3, < 14.0) + rake-compiler (~> 1.0) + xcodeproj (~> 1.22) + fastlane-sirp (1.1.0) + fiddle (1.1.8) + forwardable (1.4.0) + gettext (3.5.2) + erubi + locale (>= 2.0.5) + prime + racc + text (>= 1.3.0) + gh_inspector (1.1.3) + git (1.19.1) + addressable (~> 2.8) + rchardet (~> 1.8) + google-apis-androidpublisher_v3 (0.102.0) + google-apis-core (>= 0.15.0, < 2.a) + google-apis-core (0.18.0) + addressable (~> 2.5, >= 2.5.1) + googleauth (~> 1.9) + httpclient (>= 2.8.3, < 3.a) + mini_mime (~> 1.0) + mutex_m + representable (~> 3.0) + retriable (>= 2.0, < 4.a) + google-apis-iamcredentials_v1 (0.27.0) + google-apis-core (>= 0.15.0, < 2.a) + google-apis-playcustomapp_v1 (0.17.0) + google-apis-core (>= 0.15.0, < 2.a) + google-apis-storage_v1 (0.63.0) + google-apis-core (>= 0.15.0, < 2.a) + google-cloud-core (1.8.0) + google-cloud-env (>= 1.0, < 3.a) + google-cloud-errors (~> 1.0) + google-cloud-env (2.2.2) + base64 (~> 0.2) + faraday (>= 1.0, < 3.a) + google-cloud-errors (1.6.0) + google-cloud-storage (1.60.0) + addressable (~> 2.8) + digest-crc (~> 0.4) + google-apis-core (>= 0.18, < 2) + google-apis-iamcredentials_v1 (~> 0.18) + google-apis-storage_v1 (>= 0.42) + google-cloud-core (~> 1.6) + googleauth (~> 1.9) + mini_mime (~> 1.0) + google-logging-utils (0.2.0) + googleauth (1.17.0) + faraday (>= 1.0, < 3.a) + google-cloud-env (~> 2.2) + google-logging-utils (~> 0.1) + jwt (>= 1.4, < 4.0) + os (>= 0.9, < 2.0) + pstore (~> 0.1) + signet (>= 0.16, < 2.a) + highline (2.0.3) + http-cookie (1.0.8) + domain_name (~> 0.5) + httpclient (2.9.0) + mutex_m + java-properties (0.3.0) + jmespath (1.6.2) + json (2.19.8) + jwt (3.2.0) + base64 + locale (2.1.5) + fiddle + logger (1.7.0) + mini_magick (4.13.2) + mini_mime (1.1.5) + multi_json (1.21.1) + multipart-post (2.4.1) + mutex_m (0.3.0) + nanaimo (0.4.0) + naturally (2.3.0) + nkf (0.2.0) + nokogiri (1.19.3-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.3-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.19.3-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.3-arm-linux-musl) + racc (~> 1.4) + nokogiri (1.19.3-arm64-darwin) + racc (~> 1.4) + nokogiri (1.19.3-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.19.3-x86_64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.3-x86_64-linux-musl) + racc (~> 1.4) + octokit (6.1.1) + faraday (>= 1, < 3) + sawyer (~> 0.9) + options (2.3.2) + optparse (0.8.1) + os (1.1.4) + ostruct (0.6.3) + parallel (1.28.0) + plist (3.7.2) + prime (0.1.4) + forwardable + singleton + progress_bar (1.3.4) + highline (>= 1.6) + options (~> 2.3.0) + pstore (0.2.1) + public_suffix (7.0.5) + racc (1.8.1) + rake (13.4.2) + rake-compiler (1.3.1) + rake + rchardet (1.10.2) + representable (3.2.0) + declarative (< 0.1.0) + trailblazer-option (>= 0.1.1, < 0.2.0) + uber (< 0.2.0) + retriable (3.8.0) + rexml (3.4.4) + rouge (3.28.0) + ruby2_keywords (0.0.5) + rubyzip (2.4.1) + sawyer (0.9.3) + addressable (>= 2.3.5) + faraday (>= 0.17.3, < 3) + security (0.1.5) + signet (0.22.0) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.a) + jwt (>= 1.5, < 4.0) + simctl (1.6.10) + CFPropertyList + naturally + singleton (0.3.0) + terminal-notifier (2.0.0) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + text (1.3.1) + trailblazer-option (0.1.2) + tty-cursor (0.7.1) + tty-screen (0.8.2) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) + uber (0.1.0) + unicode-display_width (2.6.0) + word_wrap (1.0.0) + xcodeproj (1.27.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.4.0) + rexml (>= 3.3.6, < 4.0) + xcpretty (0.4.1) + rouge (~> 3.28.0) + xcpretty-travis-formatter (1.0.1) + xcpretty (~> 0.2, >= 0.0.7) + +PLATFORMS + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + x86_64-darwin + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + fastlane (~> 2.233) + fastlane-plugin-wpmreleasetoolkit (~> 14.4) + multi_json + +BUNDLED WITH + 2.5.11 diff --git a/fastlane/.gitignore b/fastlane/.gitignore new file mode 100644 index 0000000..56e5e3d --- /dev/null +++ b/fastlane/.gitignore @@ -0,0 +1,2 @@ +README.md +report.xml diff --git a/fastlane/Fastfile b/fastlane/Fastfile new file mode 100644 index 0000000..1dac3e9 --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +UI.user_error!('Please run fastlane via `bundle exec`') unless FastlaneCore::Helper.bundler? + +APPLE_TEAM_ID = 'PZYM8XX95Q' + +CODE_SIGNING_STORAGE_OPTIONS = { + storage_mode: 's3', + s3_bucket: 'a8c-fastlane-match', + s3_region: 'us-east-2' +}.freeze + +CODE_SIGNING_ENV_VARS = %w[ + MATCH_S3_ACCESS_KEY + MATCH_S3_SECRET_ACCESS_KEY + MATCH_PASSWORD +].freeze + +ASC_API_KEY_ENV_VARS = %w[ + APP_STORE_CONNECT_API_KEY_KEY_ID + APP_STORE_CONNECT_API_KEY_ISSUER_ID + APP_STORE_CONNECT_API_KEY_KEY +].freeze + +require 'fastlane/plugin/wpmreleasetoolkit' + +# wpmreleasetoolkit 14.4+ namespaces this under Fastlane::Wpmreleasetoolkit and +# exposes no top-level shortcut. +EnvManager = Fastlane::Wpmreleasetoolkit::EnvManager + +before_all do + # Sets up a temporary keychain so match works in CI. No-op locally. + setup_ci + + # `set_up` is needed even with no `.env` file so EnvManager has a configured + # instance; on CI the vars come from the Buildkite agent. + EnvManager.set_up(env_file_name: 'bridge-manager.env') +end + +desc 'Fetch the Developer ID Application certificate into the keychain' +lane :set_up_signing do |readonly: true| + CODE_SIGNING_ENV_VARS.each { |k| EnvManager.get_required_env!(k) } + ASC_API_KEY_ENV_VARS.each { |k| EnvManager.get_required_env!(k) } unless readonly + + sync_code_signing( + type: 'developer_id', + platform: 'macos', + team_id: APPLE_TEAM_ID, + app_identifier: [], + api_key: readonly ? nil : app_store_connect_api_key, + readonly: readonly, + **CODE_SIGNING_STORAGE_OPTIONS + ) +end From a4670037152314741eb1c4503df7539f6094323c Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 8 Jun 2026 13:41:59 +1000 Subject: [PATCH 02/14] Remove noisy AI comment --- fastlane/Fastfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 1dac3e9..07d93ea 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -24,8 +24,6 @@ ASC_API_KEY_ENV_VARS = %w[ require 'fastlane/plugin/wpmreleasetoolkit' -# wpmreleasetoolkit 14.4+ namespaces this under Fastlane::Wpmreleasetoolkit and -# exposes no top-level shortcut. EnvManager = Fastlane::Wpmreleasetoolkit::EnvManager before_all do From 8ad2f8f175701171696f70d07ca35e4593acbb8d Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 8 Jun 2026 13:48:46 +1000 Subject: [PATCH 03/14] Vendor gems locally and lock ruby platform Add `.bundle/config` (mirroring platform-imessage): `BUNDLE_PATH: vendor/bundle` keeps gems in the repo and out of the agent's system Ruby, and `BUNDLE_FORCE_RUBY_PLATFORM` pins the pure-Ruby gem variants so the lock is portable between a developer's Mac and the CI agents. Regenerate `Gemfile.lock` from a fresh resolve so its `PLATFORMS` is `ruby` only, and gitignore the vendored `vendor/bundle/`. --- Generated with the help of Claude Code, https://code.claude.com Co-Authored-By: Claude Opus 4.8 (1M context) --- .bundle/config | 4 ++++ .gitignore | 1 + Gemfile.lock | 27 ++++----------------------- 3 files changed, 9 insertions(+), 23 deletions(-) create mode 100644 .bundle/config diff --git a/.bundle/config b/.bundle/config new file mode 100644 index 0000000..5429866 --- /dev/null +++ b/.bundle/config @@ -0,0 +1,4 @@ +--- +BUNDLE_PATH: "vendor/bundle" +BUNDLE_SPECIFIC_PLATFORM: "false" +BUNDLE_FORCE_RUBY_PLATFORM: "true" diff --git a/.gitignore b/.gitignore index ab22752..99e7125 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /bbctl +vendor/bundle/ diff --git a/Gemfile.lock b/Gemfile.lock index cbc4576..b17cac3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -214,27 +214,15 @@ GEM logger (1.7.0) mini_magick (4.13.2) mini_mime (1.1.5) + mini_portile2 (2.8.9) multi_json (1.21.1) multipart-post (2.4.1) mutex_m (0.3.0) nanaimo (0.4.0) naturally (2.3.0) nkf (0.2.0) - nokogiri (1.19.3-aarch64-linux-gnu) - racc (~> 1.4) - nokogiri (1.19.3-aarch64-linux-musl) - racc (~> 1.4) - nokogiri (1.19.3-arm-linux-gnu) - racc (~> 1.4) - nokogiri (1.19.3-arm-linux-musl) - racc (~> 1.4) - nokogiri (1.19.3-arm64-darwin) - racc (~> 1.4) - nokogiri (1.19.3-x86_64-darwin) - racc (~> 1.4) - nokogiri (1.19.3-x86_64-linux-gnu) - racc (~> 1.4) - nokogiri (1.19.3-x86_64-linux-musl) + nokogiri (1.19.3) + mini_portile2 (~> 2.8.2) racc (~> 1.4) octokit (6.1.1) faraday (>= 1, < 3) @@ -304,14 +292,7 @@ GEM xcpretty (~> 0.2, >= 0.0.7) PLATFORMS - aarch64-linux-gnu - aarch64-linux-musl - arm-linux-gnu - arm-linux-musl - arm64-darwin - x86_64-darwin - x86_64-linux-gnu - x86_64-linux-musl + ruby DEPENDENCIES fastlane (~> 2.233) From b93c69474a4137357107e27a856fe3e46381887e Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 8 Jun 2026 13:55:12 +1000 Subject: [PATCH 04/14] Pin fastlane and toolkit floors to latest Pin `~> 2.235` (fastlane) and `~> 14.6` (wpmreleasetoolkit), the current latest on RubyGems, instead of stale floors. Lock regenerated from a fresh resolve. --- Generated with the help of Claude Code, https://code.claude.com Co-Authored-By: Claude Opus 4.8 (1M context) --- Gemfile | 4 ++-- Gemfile.lock | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 61de0c9..c9f37a2 100644 --- a/Gemfile +++ b/Gemfile @@ -2,8 +2,8 @@ source 'https://rubygems.org' -gem 'fastlane', '~> 2.233' -gem 'fastlane-plugin-wpmreleasetoolkit', '~> 14.4' +gem 'fastlane', '~> 2.235' +gem 'fastlane-plugin-wpmreleasetoolkit', '~> 14.6' # fastlane <= 2.235.0 crashes at startup on Ruby 3.3+ with "multi_json is not # part of the bundle": Google stopped pulling multi_json transitively # (googleapis/google-api-ruby-client#26611) and fastlane eagerly loads its diff --git a/Gemfile.lock b/Gemfile.lock index b17cac3..204d9fa 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -295,8 +295,8 @@ PLATFORMS ruby DEPENDENCIES - fastlane (~> 2.233) - fastlane-plugin-wpmreleasetoolkit (~> 14.4) + fastlane (~> 2.235) + fastlane-plugin-wpmreleasetoolkit (~> 14.6) multi_json BUNDLED WITH From 3ecf2b32623fead87d892196e8b6d66f084c40cb Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 8 Jun 2026 14:45:02 +1000 Subject: [PATCH 05/14] Add Buildkite sign + notarize pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Builds the macOS binaries with Go, fetches the Developer ID cert via `fastlane set_up_signing`, and signs + notarizes them with the a8c-ci-toolkit `sign_and_notarize` command (no entitlements — plain CLI under hardened runtime). Signed `bbctl-macos-{amd64,arm64}` + `sha256sums.txt` are uploaded as build artifacts; runs on every build, mirroring the platform-imessage pipeline. Publishing the signed binaries to GitHub Releases is intentionally out of scope here: `gh` auth on the mac agents is unverified, and whether release publishing moves off GitHub Actions is still open. The toolkit pin is temporary (a branch) until the command ships in a tagged release. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Opus 4.8 (1M context) --- .buildkite/commands/sign-macos-binaries.sh | 30 ++++++++++++++++++++++ .buildkite/pipeline.yml | 21 +++++++++++++++ .buildkite/shared-pipeline-vars | 12 +++++++++ 3 files changed, 63 insertions(+) create mode 100755 .buildkite/commands/sign-macos-binaries.sh create mode 100644 .buildkite/pipeline.yml create mode 100644 .buildkite/shared-pipeline-vars diff --git a/.buildkite/commands/sign-macos-binaries.sh b/.buildkite/commands/sign-macos-binaries.sh new file mode 100755 index 0000000..503e063 --- /dev/null +++ b/.buildkite/commands/sign-macos-binaries.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Go isn't guaranteed on the macOS signing image, which exists primarily for the +# Xcode signing toolchain. Install via Homebrew if absent; `go build` then +# auto-fetches the toolchain pinned in go.mod (toolchain go1.26.0). +if ! command -v go >/dev/null 2>&1; then + echo "--- :package: installing go" + brew install go +fi +go version + +echo "--- :hammer_and_wrench: build macOS binaries" +# Same names as the published release assets, so the signed binaries are drop-in. +GOOS=darwin GOARCH=amd64 ./build.sh -o bbctl-macos-amd64 +GOOS=darwin GOARCH=arm64 ./build.sh -o bbctl-macos-arm64 + +echo "--- :key: fetch Developer ID cert into the agent keychain" +install_gems +bundle exec fastlane set_up_signing + +echo "--- :apple: sign + notarize" +# Plain CLI under hardened runtime — no entitlements. The toolkit command +# resolves the Developer ID identity from the keychain by team id. +sign_and_notarize bbctl-macos-amd64 bbctl-macos-arm64 + +echo "--- :lock: checksums" +shasum -a 256 bbctl-macos-amd64 bbctl-macos-arm64 > sha256sums.txt +cat sha256sums.txt diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml new file mode 100644 index 0000000..71b1314 --- /dev/null +++ b/.buildkite/pipeline.yml @@ -0,0 +1,21 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/buildkite/pipeline-schema/main/schema.json +--- + +agents: + queue: mac + +env: + IMAGE_ID: $IMAGE_ID + +steps: + - label: ":apple: Build, sign, notarize macOS binaries" + key: sign-macos + command: .buildkite/commands/sign-macos-binaries.sh + plugins: [$CI_TOOLKIT_PLUGIN] + artifact_paths: + - bbctl-macos-amd64 + - bbctl-macos-arm64 + - sha256sums.txt + notify: + - github_commit_status: + context: Sign & Notarize macOS diff --git a/.buildkite/shared-pipeline-vars b/.buildkite/shared-pipeline-vars new file mode 100644 index 0000000..58fe722 --- /dev/null +++ b/.buildkite/shared-pipeline-vars @@ -0,0 +1,12 @@ +#!/bin/sh + +# Sourced before `buildkite-agent pipeline upload` so the values land in the rendered pipeline. + +# bbctl is a Go CLI; the macOS image is only needed for the signing toolchain +# (codesign / notarytool) and the WWDR intermediates baked into the Xcode image. +export IMAGE_ID='xcode-26.3' + +# TEMPORARY: pinned to the branch adding the macOS `sign_and_notarize` command. +# Revert to a released tag (>= the version that ships it) before merging. +CI_TOOLKIT_PLUGIN_VERSION='mokagio/macos-sign-and-notarize' +export CI_TOOLKIT_PLUGIN="automattic/a8c-ci-toolkit#$CI_TOOLKIT_PLUGIN_VERSION" From 5ce66ceba58dcde46ee72adf73d7e4fada177633 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 8 Jun 2026 15:03:02 +1000 Subject: [PATCH 06/14] Derive signing image from .xcode-version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Track the latest macOS signing image (26.5) via `.xcode-version` instead of hardcoding it, matching the platform-imessage convention. bbctl is a Go CLI with no Xcode dependency — the image only supplies codesign/notarytool and the WWDR intermediates — so it tracks latest rather than pinning a version. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Opus 4.8 (1M context) --- .buildkite/shared-pipeline-vars | 6 ++++-- .xcode-version | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 .xcode-version diff --git a/.buildkite/shared-pipeline-vars b/.buildkite/shared-pipeline-vars index 58fe722..32f413d 100644 --- a/.buildkite/shared-pipeline-vars +++ b/.buildkite/shared-pipeline-vars @@ -3,8 +3,10 @@ # Sourced before `buildkite-agent pipeline upload` so the values land in the rendered pipeline. # bbctl is a Go CLI; the macOS image is only needed for the signing toolchain -# (codesign / notarytool) and the WWDR intermediates baked into the Xcode image. -export IMAGE_ID='xcode-26.3' +# (codesign / notarytool) and the WWDR intermediates baked into the Xcode image, +# so it just tracks the latest available image via `.xcode-version`. +XCODE_VERSION=$(sed -E 's/^~> ?//' .xcode-version) +export IMAGE_ID="xcode-$XCODE_VERSION" # TEMPORARY: pinned to the branch adding the macOS `sign_and_notarize` command. # Revert to a released tag (>= the version that ships it) before merging. diff --git a/.xcode-version b/.xcode-version new file mode 100644 index 0000000..58eede2 --- /dev/null +++ b/.xcode-version @@ -0,0 +1 @@ +26.5 From 9402efd95dbb5b1741b3031f8747e76090bedd8c Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 8 Jun 2026 15:16:59 +1000 Subject: [PATCH 07/14] Pin signing image to Xcode 26.3 for nokogiri The Xcode 26.5 image's clang cannot compile `nokogiri` 1.19.3 from source (`gumbo.c: 'nokogiri_gumbo.h' file not found`), which fails `bundle install` in the signing job. `fastlane` pulls `nokogiri` in transitively via `wpmreleasetoolkit`, and `.bundle/config` forces the Ruby (source) platform, so the precompiled darwin gem is never used. Pin to 26.3, which compiles it, and record the reason as a note in `.xcode-version`. The `shared-pipeline-vars` parser now skips comment and blank lines so the note doesn't leak into `IMAGE_ID`. --- Generated with the help of Claude Code, https://claude.com/claude-code Co-Authored-By: Claude Opus 4.8 (1M context) --- .buildkite/shared-pipeline-vars | 6 +++--- .xcode-version | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.buildkite/shared-pipeline-vars b/.buildkite/shared-pipeline-vars index 32f413d..1e80130 100644 --- a/.buildkite/shared-pipeline-vars +++ b/.buildkite/shared-pipeline-vars @@ -3,9 +3,9 @@ # Sourced before `buildkite-agent pipeline upload` so the values land in the rendered pipeline. # bbctl is a Go CLI; the macOS image is only needed for the signing toolchain -# (codesign / notarytool) and the WWDR intermediates baked into the Xcode image, -# so it just tracks the latest available image via `.xcode-version`. -XCODE_VERSION=$(sed -E 's/^~> ?//' .xcode-version) +# (codesign / notarytool) and the WWDR intermediates baked into the Xcode image. +# The version comes from `.xcode-version` — first non-comment line, optional `~>`. +XCODE_VERSION=$(grep -Ev '^[[:space:]]*(#|$)' .xcode-version | head -n1 | sed -E 's/^[[:space:]]*//; s/^~> ?//; s/[[:space:]]*$//') export IMAGE_ID="xcode-$XCODE_VERSION" # TEMPORARY: pinned to the branch adding the macOS `sign_and_notarize` command. diff --git a/.xcode-version b/.xcode-version index 58eede2..bec6174 100644 --- a/.xcode-version +++ b/.xcode-version @@ -1 +1,5 @@ -26.5 +# Pinned, not latest. nokogiri 1.19.3 fails to compile from source on the Xcode +# 26.5 image's clang (gumbo.c: 'nokogiri_gumbo.h' file not found); fastlane pulls +# nokogiri in transitively via wpmreleasetoolkit. 26.3 builds it. Bump once a +# newer image compiles nokogiri, or the lockfile moves to precompiled darwin gems. +26.3 From 7e74a7afc575de3934853fb498e27b8a0b582e19 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 8 Jun 2026 15:26:40 +1000 Subject: [PATCH 08/14] Condense or remove AI-generated comments Co-authored-by: Gio Lodi --- .buildkite/commands/sign-macos-binaries.sh | 9 +++------ .buildkite/shared-pipeline-vars | 3 --- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/.buildkite/commands/sign-macos-binaries.sh b/.buildkite/commands/sign-macos-binaries.sh index 503e063..cb0bf97 100755 --- a/.buildkite/commands/sign-macos-binaries.sh +++ b/.buildkite/commands/sign-macos-binaries.sh @@ -2,9 +2,8 @@ set -euo pipefail -# Go isn't guaranteed on the macOS signing image, which exists primarily for the -# Xcode signing toolchain. Install via Homebrew if absent; `go build` then -# auto-fetches the toolchain pinned in go.mod (toolchain go1.26.0). +# We don't care about the specific Go version, only that Go is available. +# `go build` will then fetch the desired version. if ! command -v go >/dev/null 2>&1; then echo "--- :package: installing go" brew install go @@ -12,7 +11,6 @@ fi go version echo "--- :hammer_and_wrench: build macOS binaries" -# Same names as the published release assets, so the signed binaries are drop-in. GOOS=darwin GOARCH=amd64 ./build.sh -o bbctl-macos-amd64 GOOS=darwin GOARCH=arm64 ./build.sh -o bbctl-macos-arm64 @@ -21,8 +19,7 @@ install_gems bundle exec fastlane set_up_signing echo "--- :apple: sign + notarize" -# Plain CLI under hardened runtime — no entitlements. The toolkit command -# resolves the Developer ID identity from the keychain by team id. +# sing_and_notarize comes from the CI toolkit plugin sign_and_notarize bbctl-macos-amd64 bbctl-macos-arm64 echo "--- :lock: checksums" diff --git a/.buildkite/shared-pipeline-vars b/.buildkite/shared-pipeline-vars index 1e80130..e430b59 100644 --- a/.buildkite/shared-pipeline-vars +++ b/.buildkite/shared-pipeline-vars @@ -2,9 +2,6 @@ # Sourced before `buildkite-agent pipeline upload` so the values land in the rendered pipeline. -# bbctl is a Go CLI; the macOS image is only needed for the signing toolchain -# (codesign / notarytool) and the WWDR intermediates baked into the Xcode image. -# The version comes from `.xcode-version` — first non-comment line, optional `~>`. XCODE_VERSION=$(grep -Ev '^[[:space:]]*(#|$)' .xcode-version | head -n1 | sed -E 's/^[[:space:]]*//; s/^~> ?//; s/[[:space:]]*$//') export IMAGE_ID="xcode-$XCODE_VERSION" From 39515757ca01209e7b2be622e353a61694221cb8 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 8 Jun 2026 15:27:57 +1000 Subject: [PATCH 09/14] Pin ci-toolkit plugin to a commit, not a branch `mokagio/macos-sign-and-notarize` is a moving ref; pinning the current tip (`8a67edf`, "Log applied cert/team after signing") makes the signing build reproducible until the command ships in a released tag. --- Generated with the help of Claude Code, https://claude.com/claude-code Co-Authored-By: Claude Opus 4.8 (1M context) --- .buildkite/shared-pipeline-vars | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.buildkite/shared-pipeline-vars b/.buildkite/shared-pipeline-vars index e430b59..a38a737 100644 --- a/.buildkite/shared-pipeline-vars +++ b/.buildkite/shared-pipeline-vars @@ -5,7 +5,8 @@ XCODE_VERSION=$(grep -Ev '^[[:space:]]*(#|$)' .xcode-version | head -n1 | sed -E 's/^[[:space:]]*//; s/^~> ?//; s/[[:space:]]*$//') export IMAGE_ID="xcode-$XCODE_VERSION" -# TEMPORARY: pinned to the branch adding the macOS `sign_and_notarize` command. -# Revert to a released tag (>= the version that ships it) before merging. -CI_TOOLKIT_PLUGIN_VERSION='mokagio/macos-sign-and-notarize' +# TEMPORARY: pinned to a commit on the `mokagio/macos-sign-and-notarize` branch +# adding the macOS `sign_and_notarize` command. Revert to a released tag +# (>= the version that ships it) before merging. +CI_TOOLKIT_PLUGIN_VERSION='8a67edfc19a7fd04de7033d790bd1a7a4f0f8b4d' export CI_TOOLKIT_PLUGIN="automattic/a8c-ci-toolkit#$CI_TOOLKIT_PLUGIN_VERSION" From fa1d7661b4e2fc55a53826a30860276be262d039 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 8 Jun 2026 15:36:50 +1000 Subject: [PATCH 10/14] Tighten the .xcode-version pin note --- Generated with the help of Claude Code, https://claude.com/claude-code Co-Authored-By: Claude Opus 4.8 (1M context) --- .xcode-version | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.xcode-version b/.xcode-version index bec6174..aaaa15c 100644 --- a/.xcode-version +++ b/.xcode-version @@ -1,5 +1,4 @@ -# Pinned, not latest. nokogiri 1.19.3 fails to compile from source on the Xcode -# 26.5 image's clang (gumbo.c: 'nokogiri_gumbo.h' file not found); fastlane pulls -# nokogiri in transitively via wpmreleasetoolkit. 26.3 builds it. Bump once a -# newer image compiles nokogiri, or the lockfile moves to precompiled darwin gems. +# Pinned: nokogiri 1.19.3 won't compile from source on the Xcode 26.5 image +# (its clang breaks the bundled gumbo build). 26.3 builds it. Bump once a newer +# image compiles nokogiri, or the lockfile moves to precompiled darwin gems. 26.3 From 9378429c1822c3c93d4556b0465ea15d8a159fe4 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Mon, 8 Jun 2026 15:51:11 +1000 Subject: [PATCH 11/14] Drop macOS build from GitHub Actions Buildkite now compiles, signs, and notarizes the macOS binaries on a real Mac and publishes them under the same names. The unsigned GitHub Actions copies were redundant and unused, so stop building and uploading them. --- Generated with the help of Claude Code, https://claude.com/claude-code Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/go.yaml | 14 -------------- ci-build-all.sh | 2 -- 2 files changed, 16 deletions(-) diff --git a/.github/workflows/go.yaml b/.github/workflows/go.yaml index a3e79ed..5441f37 100644 --- a/.github/workflows/go.yaml +++ b/.github/workflows/go.yaml @@ -59,20 +59,6 @@ jobs: path: bbctl-linux-arm64 if-no-files-found: error - - name: Upload macos/amd64 artifact - uses: actions/upload-artifact@v6 - with: - name: bbctl-macos-amd64 - path: bbctl-macos-amd64 - if-no-files-found: error - - - name: Upload macos/arm64 artifact - uses: actions/upload-artifact@v6 - with: - name: bbctl-macos-arm64 - path: bbctl-macos-arm64 - if-no-files-found: error - build-docker: runs-on: ${{ matrix.runs-on }} strategy: diff --git a/ci-build-all.sh b/ci-build-all.sh index 28403f1..087e3cb 100755 --- a/ci-build-all.sh +++ b/ci-build-all.sh @@ -1,5 +1,3 @@ #!/bin/sh GOOS=linux GOARCH=amd64 ./build.sh -o bbctl-linux-amd64 GOOS=linux GOARCH=arm64 ./build.sh -o bbctl-linux-arm64 -GOOS=darwin GOARCH=amd64 ./build.sh -o bbctl-macos-amd64 -GOOS=darwin GOARCH=arm64 ./build.sh -o bbctl-macos-arm64 From 6135daa141af517099f0349c0573a676f62a2dc6 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Tue, 9 Jun 2026 10:46:35 +1000 Subject: [PATCH 12/14] Fix typo Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .buildkite/commands/sign-macos-binaries.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/commands/sign-macos-binaries.sh b/.buildkite/commands/sign-macos-binaries.sh index cb0bf97..0603f27 100755 --- a/.buildkite/commands/sign-macos-binaries.sh +++ b/.buildkite/commands/sign-macos-binaries.sh @@ -19,7 +19,7 @@ install_gems bundle exec fastlane set_up_signing echo "--- :apple: sign + notarize" -# sing_and_notarize comes from the CI toolkit plugin +# sign_and_notarize comes from the CI toolkit plugin sign_and_notarize bbctl-macos-amd64 bbctl-macos-arm64 echo "--- :lock: checksums" From a4774202e29792627fdd56dc25ccc6d2eced5a8e Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Tue, 9 Jun 2026 10:47:42 +1000 Subject: [PATCH 13/14] Clarify nokogiri setup in comment Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .xcode-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.xcode-version b/.xcode-version index aaaa15c..1575509 100644 --- a/.xcode-version +++ b/.xcode-version @@ -1,4 +1,4 @@ # Pinned: nokogiri 1.19.3 won't compile from source on the Xcode 26.5 image # (its clang breaks the bundled gumbo build). 26.3 builds it. Bump once a newer -# image compiles nokogiri, or the lockfile moves to precompiled darwin gems. +# image compiles nokogiri from source (we force the Ruby platform via .bundle/config). 26.3 From df4d3f7903c1bf32bed29c0db93fb69517a5d0c9 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Wed, 10 Jun 2026 11:08:19 +1000 Subject: [PATCH 14/14] Bump fastlane to latest, 2.236.0, to remove multi_json constraint --- Gemfile | 9 +-------- Gemfile.lock | 10 +++++----- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/Gemfile b/Gemfile index c9f37a2..29ed5e0 100644 --- a/Gemfile +++ b/Gemfile @@ -2,12 +2,5 @@ source 'https://rubygems.org' -gem 'fastlane', '~> 2.235' +gem 'fastlane', '~> 2.236' gem 'fastlane-plugin-wpmreleasetoolkit', '~> 14.6' -# fastlane <= 2.235.0 crashes at startup on Ruby 3.3+ with "multi_json is not -# part of the bundle": Google stopped pulling multi_json transitively -# (googleapis/google-api-ruby-client#26611) and fastlane eagerly loads its -# Google Play actions, which require it through representable. fastlane re-added -# it as a direct dependency for 2.236.0 (fastlane/fastlane#30062) — drop this -# line once the lock is on fastlane >= 2.236.0. -gem 'multi_json' diff --git a/Gemfile.lock b/Gemfile.lock index 204d9fa..99fbe5d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -8,7 +8,7 @@ GEM artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.4.0) - aws-partitions (1.1258.0) + aws-partitions (1.1259.0) aws-sdk-core (3.251.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) @@ -77,7 +77,7 @@ GEM faraday_middleware (1.2.1) faraday (~> 1.0) fastimage (2.4.1) - fastlane (2.235.0) + fastlane (2.236.0) CFPropertyList (>= 2.3, < 5.0.0) abbrev (~> 0.1) addressable (>= 2.8, < 3.0.0) @@ -106,9 +106,10 @@ GEM highline (~> 2.0) http-cookie (~> 1.0.5) json (< 3.0.0) - jwt (>= 2.1.0, < 4) + jwt (>= 2.10.3, < 4) logger (>= 1.6, < 2.0) mini_magick (>= 4.9.4, < 5.0.0) + multi_json (~> 1.12) multipart-post (>= 2.0.0, < 3.0.0) mutex_m (~> 0.3) naturally (~> 2.2) @@ -295,9 +296,8 @@ PLATFORMS ruby DEPENDENCIES - fastlane (~> 2.235) + fastlane (~> 2.236) fastlane-plugin-wpmreleasetoolkit (~> 14.6) - multi_json BUNDLED WITH 2.5.11