Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
62d0f78
Bump the a8c CI toolkit plugin to 6.3.0
mokagio Aug 31, 2026
4ba6e77
Install Go on the agents instead of requiring it
mokagio Aug 31, 2026
4f8f933
Fix the dropped arch suffix in the build scripts
mokagio Aug 31, 2026
d2fef62
Lint shell scripts on CI
mokagio Aug 31, 2026
01d84ac
Bump the release toolkit to 15.0
mokagio Aug 31, 2026
df45130
Use the release toolkit's EnvManager
mokagio Aug 31, 2026
f3c0908
Check the credentials the lane actually uses
mokagio Aug 31, 2026
4e8c32a
Ask match for the certificate, not a profile
mokagio Aug 31, 2026
ac3947a
Ignore the files fastlane regenerates
mokagio Aug 31, 2026
8dc59e2
Remove the unused PROJECT_ROOT_FOLDER constant
mokagio Aug 31, 2026
1165b41
Track Gemfile.lock
mokagio Aug 31, 2026
398e15f
Pin the macOS VM image via .xcode-version
mokagio Aug 31, 2026
bb3f070
Vendor Ruby gems in-repo
mokagio Aug 31, 2026
c6fb1e1
Match studio's bundler retry and jobs settings
mokagio Aug 31, 2026
cc417a7
Pin fastlane to 2.238
mokagio Aug 31, 2026
f6221d4
Pin Ruby 3.4.9 via .ruby-version
mokagio Aug 31, 2026
1660da6
Drop the release-toolkit EnvManager
mokagio Aug 31, 2026
57eae57
Sign macOS binaries through fastlane
mokagio Aug 31, 2026
67df9b7
Sign macOS artifacts on every build
mokagio Aug 31, 2026
a76714c
Sign Windows with Azure Trusted Signing
mokagio Aug 31, 2026
5c6e666
Remove leftover infra TODO markers
mokagio Aug 31, 2026
9136114
Install Linux Go from the official tarball
mokagio Aug 31, 2026
7fa71aa
Fix Prettier wrapping in BUILD-SIGNING.md
mokagio Aug 31, 2026
3cf4794
Stamp vip-next as 5.0.0-dev.<sha>
mokagio Sep 1, 2026
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
28 changes: 21 additions & 7 deletions .buildkite/build-linux.sh
Original file line number Diff line number Diff line change
@@ -1,20 +1,35 @@
#!/usr/bin/env bash
set -euo pipefail
# Build the Linux vip-next binaries + checksums on a Buildkite Linux agent.
# Linux has no OS-enforced executable signature; we publish checksums (a detached
# GPG/cosign signature is optional — see the bottom of this file).
# Linux has no OS-enforced executable signature; we publish checksums.

[ -f .buildkite/shared-pipeline-vars ] && . .buildkite/shared-pipeline-vars
: "${BIN_BASE:=vip-next}"

command -v go >/dev/null 2>&1 || { echo "go not found; agent must provide Go ${GO_VERSION:-1.27}+" >&2; exit 1; }
if ! command -v go >/dev/null 2>&1; then
echo "--- :package: install go"
version="$(awk '/^toolchain / { print $2; exit }' go.mod)"
version="${version#go}"
[ -n "${version}" ] || { echo "no toolchain directive in go.mod" >&2; exit 1; }
case "$(uname -m)" in
x86_64|amd64) arch=amd64 ;;
aarch64|arm64) arch=arm64 ;;
*) echo "unsupported arch $(uname -m)" >&2; exit 1 ;;
esac
# Not `$HOME/go` — that is GOPATH.
prefix="${HOME}/.local"
mkdir -p "${prefix}"
curl -fsSL "https://go.dev/dl/go${version}.linux-${arch}.tar.gz" | tar -C "${prefix}" -xz
export PATH="${prefix}/go/bin:${PATH}"
fi
go version

VERSION="$(git describe --tags --always --dirty 2>/dev/null || echo dev)"
VERSION="$(go run -mod=mod ./cmd/stamp-version)"
COMMIT="$(git rev-parse --short HEAD 2>/dev/null || echo unknown)"

build() {
local goarch="$1" out="dist/${BIN_BASE}-linux-${goarch}"
local goarch="$1"
local out="dist/${BIN_BASE}-linux-${goarch}"
echo "--- :go: build linux/${goarch}"
CGO_ENABLED=0 GOOS=linux GOARCH="${goarch}" \
go build -buildvcs=false -trimpath \
Expand All @@ -39,5 +54,4 @@ if [ -n "${native}" ]; then
"dist/${BIN_BASE}-linux-${native}" whoami --help
fi

# Optional detached signature (needs an infra-owned key); checksums-only by default.
# gpg --armor --detach-sign "dist/${BIN_BASE}-linux-amd64" # ← infra: enable if desired

67 changes: 17 additions & 50 deletions .buildkite/build-macos.sh
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail
# Build, and on tag builds sign + notarize, the macOS vip-next artifacts, on a
# Buildkite macOS agent (queue: mac):
# - two bare per-arch binaries: codesigned + notarized (online-verified; a bare
# Mach-O can't be stapled)
# - one universal .pkg installer: codesigned + productsigned + notarized + STAPLED
# (offline-verified)
# Checksums are written AFTER signing (signing changes the bytes).
# Build, sign, and notarize the macOS vip-next artifacts on a Buildkite macOS
# agent (queue: mac). Two bare per-arch binaries: codesigned + notarized
# (online-verified; a bare Mach-O can't be stapled). Checksums are written
# AFTER signing (signing changes the bytes).

[ -f .buildkite/shared-pipeline-vars ] && . .buildkite/shared-pipeline-vars
: "${BIN_BASE:=vip-next}"
Expand All @@ -15,14 +12,22 @@ echo "--- :ruby: install gems"
if command -v install_gems >/dev/null 2>&1; then install_gems; else bundle install; fi

echo "--- :go: toolchain"
command -v go >/dev/null 2>&1 || { echo "go not found; agent must provide Go ${GO_VERSION:-1.27}+" >&2; exit 1; }
# Any Go will do: go.mod's `toolchain` directive makes it fetch go1.27.0 itself.
if ! command -v go >/dev/null 2>&1; then
echo "--- :package: install go"
brew install go
fi
go version

VERSION="$(git describe --tags --always --dirty 2>/dev/null || echo dev)"
# Bundler installs to `vendor/bundle`, which makes Go take `-mod=vendor`.
export GOFLAGS="${GOFLAGS:--mod=mod}"

VERSION="$(go run -mod=mod ./cmd/stamp-version)"
COMMIT="$(git rev-parse --short HEAD 2>/dev/null || echo unknown)"

build() {
local goarch="$1" out="dist/${BIN_BASE}-darwin-${goarch}"
local goarch="$1"
local out="dist/${BIN_BASE}-darwin-${goarch}"
echo "--- :go: build darwin/${goarch}"
CGO_ENABLED=0 GOOS=darwin GOARCH="${goarch}" \
go build -buildvcs=false -trimpath \
Expand All @@ -48,50 +53,12 @@ if [ -n "${native}" ]; then
"dist/${BIN_BASE}-darwin-${native}" whoami --help
fi

if [ -z "${BUILDKITE_TAG:-}" ]; then
echo "--- not a tag build; skipping sign/notarize (unsigned checksums only)"
checksum "dist/${BIN_BASE}-darwin-arm64"
checksum "dist/${BIN_BASE}-darwin-amd64"
exit 0
fi

echo "--- :closed_lock_with_key: fetch signing certs (fastlane match)"
bundle exec fastlane configure_code_signing

# Build the universal binary from the UNSIGNED arches, then sign all three once.
uni="dist/${BIN_BASE}-darwin-universal"
lipo -create -output "${uni}" "dist/${BIN_BASE}-darwin-arm64" "dist/${BIN_BASE}-darwin-amd64"

for bin in "dist/${BIN_BASE}-darwin-arm64" "dist/${BIN_BASE}-darwin-amd64" "${uni}"; do
echo "--- :closed_lock_with_key: codesign ${bin}"
codesign --remove-signature "${bin}" 2>/dev/null || true # drop Go's ad-hoc sig
codesign --sign "${MACOS_SIGN_IDENTITY}" --options runtime --timestamp --force "${bin}"
codesign --verify --strict --verbose=2 "${bin}"
done

# Bare binaries: notarize (no staple — nothing to hold the ticket), then checksum.
for arch in arm64 amd64; do
bin="dist/${BIN_BASE}-darwin-${arch}"
echo "--- :cloud: notarize ${arch} (no staple)"
ditto -c -k --keepParent "${bin}" "${bin}.zip"
bundle exec fastlane notarize_artifact path:"${bin}.zip"
rm -f "${bin}.zip"
echo "--- :closed_lock_with_key: sign and notarize ${arch}"
bundle exec fastlane sign_and_notarize binary:"${bin}"
checksum "${bin}"
done

# Universal .pkg: package the signed universal binary → sign the pkg → notarize + staple.
echo "--- :package: build + sign universal .pkg"
pkgroot="$(mktemp -d)"
cp "${uni}" "${pkgroot}/${BIN_BASE}"
pkg="dist/${BIN_BASE}-darwin-universal.pkg"
pkgbuild --root "${pkgroot}" --identifier com.automattic.vip-cli --version "${VERSION}" \
--install-location /usr/local/bin "${pkg}.unsigned"
productsign --sign "${MACOS_INSTALLER_IDENTITY}" "${pkg}.unsigned" "${pkg}"
rm -f "${pkg}.unsigned"
rm -rf "${pkgroot}"

echo "--- :cloud: notarize + staple .pkg"
bundle exec fastlane notarize_artifact path:"${pkg}" skip_stapling:false
xcrun stapler validate "${pkg}"
checksum "${uni}"
checksum "${pkg}"
59 changes: 33 additions & 26 deletions .buildkite/build-windows.ps1
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Requires -Version 5.1
# Build vip-next.exe and (on tag builds) Authenticode-sign it, on a Buildkite
# Windows agent. Checksum is computed AFTER signing (signing changes the bytes).
# Build and Authenticode-sign vip-next.exe on a Buildkite Windows agent.
# Checksum is computed AFTER signing (signing changes the bytes).
$ErrorActionPreference = 'Stop'

$binBase = if ($env:BIN_BASE) { $env:BIN_BASE } else { 'vip-next' }
Expand All @@ -9,12 +9,25 @@ function Get-GitOr($cmd, $fallback) {
try { $v = & git @cmd 2>$null; if ($LASTEXITCODE -eq 0 -and $v) { return $v.Trim() } } catch {}
return $fallback
}
$version = Get-GitOr @('describe','--tags','--always','--dirty') 'dev'
$commit = Get-GitOr @('rev-parse','--short','HEAD') 'unknown'

New-Item -ItemType Directory -Force -Path dist | Out-Null
$out = "dist/$binBase-windows-amd64.exe"

# Any Go will do: go.mod's `toolchain` directive makes it fetch go1.27.0 itself.
if (-not (Get-Command go -ErrorAction SilentlyContinue)) {
Write-Host "--- :package: install go"
choco install golang -y --no-progress
if ($LASTEXITCODE -ne 0) { throw 'choco install golang failed' }
# choco updates the machine PATH, not this process's.
$env:PATH = "$env:PATH;$env:ProgramFiles\Go\bin"
}
go version
if ($LASTEXITCODE -ne 0) { throw 'go not usable after install' }

$version = (& go run -mod=mod ./cmd/stamp-version | Out-String).Trim()
if ($LASTEXITCODE -ne 0 -or -not $version) { throw 'stamp-version failed' }
$commit = Get-GitOr @('rev-parse','--short','HEAD') 'unknown'

Write-Host "--- :go: build windows/amd64"
$env:CGO_ENABLED = '0'; $env:GOOS = 'windows'; $env:GOARCH = 'amd64'
$ldflags = "-s -w -X github.com/Automattic/vip/internal/version.Version=$version -X github.com/Automattic/vip/internal/version.Commit=$commit"
Expand All @@ -25,28 +38,22 @@ Write-Host "--- :test_tube: smoke"
& $out --version
& $out whoami --help

if ($env:BUILDKITE_TAG) {
Write-Host "--- :closed_lock_with_key: Authenticode sign"
# ← infra: confirm the Windows cert mechanism. Draft = PFX-from-base64-secret,
# mirroring the current GitHub Actions workflow. EV certs can NOT use a plain
# PFX (FIPS-hardware since June 2023) — if you use Azure Trusted Signing, swap
# the two signtool lines for `signtool sign /fd SHA256 /tr <url> /td SHA256 /dlib <dll> /dmdf <metadata> $out`.
$pfxB64 = $env:WINDOWS_CERTIFICATE_PFX_BASE64
$pfxPw = $env:WINDOWS_CERTIFICATE_PASSWORD
$ts = if ($env:WINDOWS_TIMESTAMP_URL) { $env:WINDOWS_TIMESTAMP_URL } else { 'http://timestamp.digicert.com' }
if (-not $pfxB64 -or -not $pfxPw) { throw 'tag build but WINDOWS_CERTIFICATE_PFX_BASE64 / _PASSWORD not set' }

$pfx = Join-Path $env:TEMP 'vip-codesign.pfx'
[IO.File]::WriteAllBytes($pfx, [Convert]::FromBase64String($pfxB64))
try {
signtool sign /fd SHA256 /td SHA256 /tr $ts /f $pfx /p $pfxPw $out
if ($LASTEXITCODE -ne 0) { throw 'signtool sign failed' }
signtool verify /pa /v $out
if ($LASTEXITCODE -ne 0) { throw 'signtool verify failed' }
} finally {
Remove-Item $pfx -Force -ErrorAction SilentlyContinue
}
}
Write-Host "--- :closed_lock_with_key: Azure Trusted Signing"
$setupScript = (Get-Command setup_azure_trusted_signing.ps1 -ErrorAction Stop).Source
& $setupScript
if ($LASTEXITCODE -ne 0) { throw 'setup_azure_trusted_signing.ps1 failed' }

Write-Host "--- :closed_lock_with_key: Authenticode sign"
& $env:SIGNTOOL_PATH sign /v `
/fd $env:AZURE_FILE_DIGEST `
/tr $env:AZURE_TIMESTAMP_SERVER `
/td $env:AZURE_TIMESTAMP_DIGEST `
/dlib $env:AZURE_CODE_SIGNING_DLIB `
/dmdf $env:AZURE_METADATA_JSON `
$out
if ($LASTEXITCODE -ne 0) { throw 'signtool sign failed' }
& $env:SIGNTOOL_PATH verify /pa /v $out
if ($LASTEXITCODE -ne 0) { throw 'signtool verify failed' }

Write-Host "--- checksum"
$hash = (Get-FileHash -Algorithm SHA256 $out).Hash.ToLower()
Expand Down
17 changes: 11 additions & 6 deletions .buildkite/pipeline.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/buildkite/pipeline-schema/main/schema.json
---
# Shared vars (CI_TOOLKIT_PLUGIN, GO_VERSION, BIN_BASE, signing identities) come
# from .buildkite/shared-pipeline-vars, which our setup source's before
# Shared vars (CI_TOOLKIT_PLUGIN, BIN_BASE) come from
# .buildkite/shared-pipeline-vars, which our setup source's before
# `buildkite-agent pipeline upload` interpolates this file.
#
# Every commit builds + smoke-tests all three platforms. Signing + notarization
# run only on TAG builds — gated on $BUILDKITE_TAG inside each build script.
# Every commit builds + smoke-tests all three platforms. macOS signs and
# notarizes, and Windows Authenticode-signs, on every build so those paths
# are not tag-only.

env:
DO_NOT_TRACK: '1'
Expand All @@ -17,6 +18,8 @@ steps:
- $CI_TOOLKIT_PLUGIN
agents:
queue: mac
env:
IMAGE_ID: $IMAGE_ID
notify:
- github_commit_status:
context: 'Build & sign (macOS)'
Expand All @@ -25,8 +28,10 @@ steps:

- label: ':windows: Build & sign (Windows)'
command: powershell -NoProfile -ExecutionPolicy Bypass -File .buildkite/build-windows.ps1
plugins:
- $CI_TOOLKIT_PLUGIN
agents:
queue: windows # ← infra: confirm Windows agent queue name
queue: windows
notify:
- github_commit_status:
context: 'Build & sign (Windows)'
Expand All @@ -36,7 +41,7 @@ steps:
- label: ':linux: Build (Linux)'
command: .buildkite/build-linux.sh
agents:
queue: default # ← infra: confirm Linux agent queue name
queue: default
notify:
- github_commit_status:
context: 'Build (Linux)'
Expand Down
17 changes: 6 additions & 11 deletions .buildkite/shared-pipeline-vars
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,12 @@

# a8c CI toolkit Buildkite plugin — provides install_gems, secret injection, and
# GitHub status helpers on the macOS agent.
export CI_TOOLKIT_PLUGIN="automattic/a8c-ci-toolkit#5.3.1" # ← infra: confirm current version
export CI_TOOLKIT_PLUGIN="automattic/a8c-ci-toolkit#6.3.0"

# Go toolchain the build scripts require. encoding/json/v2 is standard in Go 1.27.
export GO_VERSION="1.27" # ← infra: match your agent provisioning

# Binary base name + macOS signing identities (team PZYM8XX95Q = Automattic, Inc.).
export BIN_BASE="vip-next"
export MACOS_TEAM_ID="PZYM8XX95Q"
export MACOS_SIGN_IDENTITY="Developer ID Application: Automattic, Inc. (PZYM8XX95Q)"
export MACOS_INSTALLER_IDENTITY="Developer ID Installer: Automattic, Inc. (PZYM8XX95Q)" # ← infra: confirm this cert exists

# NOTE: unlike the reference we set no Xcode IMAGE_ID / .xcode-version — this is a
# plain Go build, no fyne/Xcode. If your mac queue requires a specific VM image,
# add: export IMAGE_ID="<image>" # ← infra
# The macOS VM to run on. The `mac` queue's command hook aborts when IMAGE_ID is
# unset, and the signing step needs `codesign` and `xcrun notarytool`
# regardless of Go being the compiler.
XCODE_VERSION=$(grep -Ev '^[[:space:]]*(#|$)' .xcode-version | head -n1 | sed -E 's/^[[:space:]]*//; s/^~> ?//; s/[[:space:]]*$//')
export IMAGE_ID="xcode-$XCODE_VERSION"
4 changes: 4 additions & 0 deletions .bundle/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
BUNDLE_PATH: "vendor/bundle"
BUNDLE_JOBS: "3"
BUNDLE_RETRY: "3"
26 changes: 26 additions & 0 deletions .github/workflows/shellcheck.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: ShellCheck

on:
pull_request:
push:
branches:
- develop
- trunk
workflow_dispatch:

permissions:
contents: read

jobs:
shellcheck:
name: Lint shell scripts
runs-on: ubuntu-latest
steps:
- name: Check out the source code
uses: actions/checkout@v7

# `-S warning` drops the `info` level, which is otherwise dominated by
# SC1091 for the `shared-pipeline-vars` file the build scripts source at
# runtime. ShellCheck ships preinstalled on the runner image.
- name: Run ShellCheck
run: git ls-files '*.sh' | xargs --no-run-if-empty shellcheck -S warning
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,11 @@ go.work.sum
# third_party/go-search-replace/MANIFEST (which IS tracked). Binaries stay out
# of git so the repo does not carry ~19 MB of executables per upgrade.
third_party/go-search-replace/*/

# Ruby tooling
vendor/bundle/

# Regenerated by fastlane on every run; `bundle exec fastlane lanes` prints the
# same lane summary as the README.
fastlane/README.md
fastlane/report.xml
1 change: 1 addition & 0 deletions .ruby-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.4.9
1 change: 1 addition & 0 deletions .xcode-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
26.6
3 changes: 1 addition & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

source 'https://rubygems.org'

gem 'fastlane', '~> 2.237'
gem 'fastlane-plugin-wpmreleasetoolkit', '~> 14.10'
gem 'fastlane', '~> 2.238'
# Avoids "certificate verify failed (unable to get certificate CRL)" on some hosts.
# See https://github.com/ruby/openssl/issues/949
gem 'openssl', '~> 4.0'
Loading
Loading