Skip to content

Make the Buildkite signing scripts runnable - #3019

Draft
mokagio wants to merge 24 commits into
trunkfrom
ainfra-3030-refine-the-vip-cli-signing-implementation-from-3013
Draft

Make the Buildkite signing scripts runnable#3019
mokagio wants to merge 24 commits into
trunkfrom
ainfra-3030-refine-the-vip-cli-signing-implementation-from-3013

Conversation

@mokagio

@mokagio mokagio commented Aug 31, 2026

Copy link
Copy Markdown
Opus blabber. It's been aweful in this PR...

Description

The Buildkite signing setup added alongside the Go CLI had never run — no pipeline was registered, and ci-go.yml's paths: filter excludes .buildkite/, so nothing read those files. The pipeline now exists, and this makes the scripts it runs capable of actually starting.

Four things would each have failed the macOS step before it reached signing:

  1. No IMAGE_ID. The mac queue's command hook aborts when it is unset. It was omitted on the assumption that a Go build needs no Xcode image — but codesign, pkgbuild, productsign and xcrun notarytool all come from Xcode regardless of what compiled the binary. Now pinned via .xcode-version, as in the iOS repos.
  2. No Go on the agents. Nothing in the agent images provisions a toolchain. Each platform now installs it the way that platform already does. The version is not pinned at the install site: GOTOOLCHAIN defaults to auto, so the new toolchain go1.27.0 directive in go.mod is what fixes the compiler, and it is now stated in one place instead of two.
  3. A dropped arch suffix. local goarch="$1" out="…-${goarch}" expands $goarch before the same local assigns it, so both arches wrote to one path and the smoke test then looked for a file that never existed. Also adds a ShellCheck workflow, since a plain warning sitting in trunk is the actual failure here.
  4. match asked for a provisioning profile. Passing an app identifier made it hunt a profile for com.automattic.vip-cli that has never existed, instead of the Developer ID certificate codesign needs. Developer ID certs are not app-scoped.

Also: the credential guards were backwards (the App Store Connect key was demanded on every run while the match credentials went unchecked), the vendored EnvManager copy is replaced by the release toolkit's, and Gemfile.lock is now tracked.

Intentional tradeoffs

Pinning Xcode 26.6, not 26.3. A sibling repo pins back because it forces the Ruby platform and nokogiri will not build from source on 26.5's clang. That does not apply here: Gemfile.lock resolves the precompiled nokogiri-1.19.4-arm64-darwin and there is no .bundle/config overriding it.

No paths: filter on the ShellCheck workflow. A paths filter is what let .buildkite/ go unlinted in the first place.

Gotchas

Signing still cannot succeed on a tag build. build-macos.sh runs productsign with a Developer ID Installer certificate that the fastlane lane never fetches — the provisioning for it is commented out. Whether to fix that or drop the .pkg and ship signed, notarized bare binaries is an open decision, tracked separately along with the go-search-replace bundling gap and Windows secret injection.

docs/BUILD-SIGNING.md still describes the previous arrangement and needs a follow-up pass; left out here to keep this reviewable.

No customer-facing change, so no changelog entry — nothing here ships in the published CLI.

Pull request checklist

Steps to Test

Everything below was run locally on macOS before opening this.

  1. bundle install && bundle exec fastlane configure_code_signing — decrypts the match bucket and installs the Developer ID Application certificate. On trunk this fails with "No matching provisioning profiles found".
  2. env -u MATCH_PASSWORD bundle exec fastlane configure_code_signing — fails at the guard naming MATCH_PASSWORD, rather than deep inside match.
  3. git ls-files '*.sh' | xargs shellcheck -S warning — clean; run it against trunk's .buildkite/build-linux.sh and it fails.
  4. ./.buildkite/build-linux.sh — produces vip-next-linux-amd64 and vip-next-linux-arm64 with matching .sha256 files. On trunk this yields a single vip-next-linux- and then dies. It exits non-zero at the smoke step on a non-Linux host, which is expected.
  5. . .buildkite/shared-pipeline-vars && echo "$IMAGE_ID" — prints xcode-26.6.
  6. go build ./cmd/vip-next — confirms the toolchain directive resolves.

The macOS and Windows steps cannot be exercised locally; the Buildkite build on this PR is the first real run.

mokagio and others added 12 commits August 31, 2026 19:16
5.3.1 was a placeholder carried over from the reference pipeline, marked
`← infra: confirm current version` because nobody had checked.
6.3.0 is the current release; `buildkite-ci`'s own pipeline upload template
is already on 5.6.0, so 5.3.1 was behind even the shared default.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
No agent image provisions a Go toolchain — nothing under `src/agents/` in
`Automattic/buildkite-ci` installs one, and there is no `mise` or `asdf`
either — so `command -v go || exit 1` failed every build before it started.

Each platform installs it the way that platform already does: the `mac` queue
has Homebrew provisioned, the `windows` queue has Chocolatey, and the `default`
queue runs Amazon Linux 2023, whose `golang` package is 1.25.12.

None of those give us 1.27, and they do not have to.
`GOTOOLCHAIN` defaults to `auto`, so any Go >= 1.21 downloads and uses the
version the module asks for.
The new `toolchain go1.27.0` directive is what pins it: the compiler is then
identical on all three platforms no matter what brew, choco or dnf shipped,
and the version lives in `go.mod` alone rather than being restated in
`GO_VERSION`.

`beeper/bridge-manager` reaches the same conclusion for the same reason — see
`.buildkite/commands/sign-macos-binaries.sh` there.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`local goarch="$1" out="…-${goarch}"` expands `$goarch` before the same
`local` has assigned it, so every artifact was written to `…-linux-` and
`…-darwin-` with no arch (verified under both bash and dash).

Both arches therefore wrote to one path, the second clobbering the first,
and the smoke test then looked for a `-amd64`/`-arm64` file that had never
existed — so both scripts died on their first run regardless of signing.

Nothing had caught it because these scripts have never executed: no pipeline
was registered, and no workflow lints `.buildkite/`.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The arch-suffix bug fixed in the previous commit was a plain ShellCheck
warning that sat in trunk because nothing reads these files: no Buildkite
pipeline is registered, and `ci-go.yml`'s `paths:` filter excludes
`.buildkite/`.

Deliberately no `paths:` filter here — a paths filter is what let the
directory go unlinted in the first place, and the repo's other small
workflows run on every PR anyway.

`-S warning` suppresses the `info` level, which is otherwise all SC1091 for
the runtime-sourced `shared-pipeline-vars`. Every tracked script passes at
that level today, so this starts green with nothing grandfathered.

`build-windows.ps1` stays uncovered; PSScriptAnalyzer is a separate job.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
15.0.0 is the current release, and its `EnvManager` is what replaces the
vendored copy in the next commit.

Neither of its breaking changes affects us: `ios_build_preflight` and
`android_build_preflight` are not used here, and `EnvManager` now layering the
`.env` into the process `ENV` is what our vendored copy already did via
`Dotenv.load`.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`fastlane/lib/env_manager.rb` was a copy of the one in `Automattic/download`,
carrying a note to replace it with the canonical version if one existed.
It does — and the plugin providing it was already a dependency, so the copy
sat unused next to the real thing.

The class-level API (`set_up`, `require_env_vars!`) is unchanged in 15.0.0,
so this is a drop-in: `configure_code_signing` still stops at the same guard
with the same message.

`beeper/bridge-manager` resolves it the same way.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`configure_code_signing` demanded the App Store Connect key on every run and
never checked the `match` credentials at all — exactly backwards for its
default path.

A readonly fetch only reads and decrypts the S3 bucket, so it needs
`MATCH_S3_*` and `MATCH_PASSWORD` and nothing from App Store Connect.
The ASC key is only needed to create or renew a certificate, which is what
`readonly: false` means.

Missing match credentials used to surface as a failure from inside `match`;
they now fail at the guard, naming the variable.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Passing an app identifier made `match` look for a provisioning profile for
`com.automattic.vip-cli`, and fail with "No matching provisioning profiles
found and cannot create a new one because you enabled `readonly`".

Nothing here needs a profile. `codesign` needs the Developer ID Application
certificate, and Developer ID certs are not app-scoped — which also settles
the open question of whether to reuse the team cert or make a new match entry
for this app: there is no per-app entry to make.

With this, `configure_code_signing` completes: it decrypts the bucket and
installs the certificate.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Running any lane writes `fastlane/README.md` and `fastlane/report.xml` into
the working tree, so both were one `git add .` away from being committed and
then churning on every subsequent run.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
It is a convention carried over from the mobile Fastfiles, which use it to
build paths. Nothing here does: no reference in this repo, the release
toolkit, or any installed gem.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Without it every agent resolved fastlane, the release toolkit and their
transitive dependencies afresh, so the gems that signed a release were
neither pinned nor reviewable — and a bad upstream release would have landed
straight in a signing run.

Checked against the macOS agents before committing, since `install_gems`
derives the Bundler version from this file: it will install Bundler 4.0.19,
which needs Ruby >= 3.2.0, and the agents' rbenv default is 3.2.2. No gem in
the lock requires a newer Ruby.

That is no headroom, though — the release toolkit's floor is exactly 3.2.2,
so a future bump to it needs the agent image moved first.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The macOS step could never have started: the `mac` queue's command hook exits
with "You must specify an IMAGE_ID to use" when the variable is unset, and it
was unset on the assumption that a Go build needs no Xcode image. It does —
`codesign`, `pkgbuild`, `productsign` and `xcrun notarytool` all come from
Xcode, whatever compiled the binary.

`.xcode-version` rather than a literal, matching wpios, wcios, pcios and
`beeper/bridge-manager`, so the pin is visible where people look for it.

26.6 is the newest image. `bridge-manager` pins 26.3 instead because it forces
the Ruby platform and nokogiri will not build from source on 26.5's clang;
that does not apply here — `Gemfile.lock` resolves the precompiled
`nokogiri-1.19.4-arm64-darwin` and there is no `.bundle/config` overriding it.

This also corrects 1165b41, which said the agents default to Ruby 3.2.2 with
no headroom. That was read off the 26.5 manifest. 26.6 defaults to 3.4.9 and
also carries 3.3.11 and 4.0.5, so the locked gems have room above the release
toolkit's 3.2.2 floor.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mokagio mokagio self-assigned this Aug 31, 2026
@github-actions

github-actions Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Dependency Review

The following issues were found:

  • ✅ 0 vulnerable package(s)
  • ✅ 0 package(s) with incompatible licenses
  • ✅ 0 package(s) with invalid SPDX license definitions
  • ⚠️ 1 package(s) with unknown licenses.
  • ⚠️ 9 packages with OpenSSF Scorecard issues.

View full job summary

mokagio and others added 12 commits August 31, 2026 20:20
The CI toolkit's `install_gems` ends with `save_cache vendor/bundle`, so
without `BUNDLE_PATH` that directory never existed: gems went to the system
gem dir and every build silently cached nothing, then reinstalled 127 gems
from scratch.

Ported from Simperium/simperium-ios@3452bee3, minus its
`BUNDLE_FORCE_RUBY_PLATFORM`. That setting makes nokogiri build from source,
which fails on current clang — `gumbo.c: fatal error: 'nokogiri_gumbo.h' file
not found`, reproduced locally. It is why `beeper/bridge-manager` is pinned
back to the Xcode 26.3 image; adopting it here would mean giving up 26.6 and
its Ruby 3.4.9 default for 26.3's 3.2.2. Nothing needs it: `Gemfile.lock`
resolves `nokogiri-1.19.4-arm64-darwin` precompiled, so there is no build to
force a platform for.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`Automattic/studio` is the closest precedent — an a8c app with the same
fastlane setup that also builds on Linux — and it sets these alongside
`BUNDLE_PATH`. Retries matter more on CI than locally, where a single
flaky gem fetch fails the whole build.

It notably does not set `BUNDLE_FORCE_RUBY_PLATFORM`, which is the other
half of the simperium-ios config and the reason that repo cannot use a
current Xcode image.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AINFRA-2927 made ~> 2.238 the floor after `bundle_id` was removed from `notarize`.
The lock already resolved 2.238.0; this only raises the constraint.

---

Generated with the help of Grok, https://grok.x.ai

Co-Authored-By: Grok 4.6 <grok@x.ai>
Matches the Xcode 26.6 image default and the wpios/pcios/Day One CLI pin, so `install_gems` cache keys off an explicit version rather than whichever rbenv default the agent happens to have.

---

Generated with the help of Grok, https://grok.x.ai

Co-Authored-By: Grok 4.6 <grok@x.ai>
Day One CLI signs without the toolkit: a 10-line helper, not a gem whose nokogiri
forces the Ruby-platform / Xcode-image dance. The plugin was only here for
`EnvManager`.

---

Generated with the help of Grok, https://grok.x.ai

Co-Authored-By: Grok 4.6 <grok@x.ai>
Day One CLI is the reference: certs, codesign with `--identifier`, verify, and
notarize live in lanes, not Bash. The `.pkg` path needed an Installer cert
`match` never fetched, so it is gone. `GOFLAGS=-mod=mod` keeps Go from treating
Bundler's `vendor/bundle` as a Go vendor tree.

---

Generated with the help of Grok, https://grok.x.ai

Co-Authored-By: Grok 4.6 <grok@x.ai>
Tag-gating is what left the 3013 path untested. Day One CLI signs every build
and only gates GitHub upload; this matches that.

---

Generated with the help of Grok, https://grok.x.ai

Co-Authored-By: Grok 4.6 <grok@x.ai>
PFX is dead for EV certs since 2023. Studio and Simplenote Electron call
`setup_azure_trusted_signing.ps1` from the CI toolkit on `queue: windows`;
the six Azure vars are already on that queue. The plugin was missing from
this step, so even the old PFX secrets would not have injected.

---

Generated with the help of Grok, https://grok.x.ai

Co-Authored-By: Grok 4.6 <grok@x.ai>
AINFRA-3030 is done when none remain in shipped files. The queue names were
already correct; the rest were unfinished decisions that this branch has now
made. Docs catch up so they no longer describe the tag-gated `.pkg`/PFX path.

---

Generated with the help of Grok, https://grok.x.ai

Co-Authored-By: Grok 4.6 <grok@x.ai>
The `default` queue has no passwordless sudo, so `dnf` cannot work. Unpack
under `$HOME/.local` (not `$HOME/go`, which is GOPATH) using the `toolchain`
version from `go.mod`.

---

Generated with the help of Grok, https://grok.x.ai

Co-Authored-By: Grok 4.6 <grok@x.ai>
---

Generated with the help of Grok, https://grok.x.ai

Co-Authored-By: Grok 4.6 <grok@x.ai>
A 5.* tag is a Go release and is used verbatim. PRs and 4.x npm tags must not
advertise a Node version, so they get 5.0.0-dev.<shortsha>.

---

Generated with the help of Grok, https://grok.x.ai

Co-Authored-By: Grok 4.6 <grok@x.ai>
@sonarqubecloud

sonarqubecloud Bot commented Sep 1, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant