ci: Authenticode-sign Windows and notarize the macOS universal binary.

Release tags now fail closed without signing secrets, ship a signed Windows PE via house signerd, and replace per-arch Darwin tarballs with one stapled universal archive.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
David Newhall II
2026-08-30 13:06:00 -07:00
co-authored by Cursor
parent 3a595a6896
commit eeda665b7e
4 changed files with 163 additions and 1 deletions
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
# Fail closed when a publishing secret is empty. GitHub maps missing secrets
# to "" so `if: secrets.FOO != ''` is not a substitute.
set -euo pipefail
missing=0
need() {
local name=$1
local val=${!name-}
if [ -z "${val}" ]; then
echo "missing secret: ${name}" >&2
missing=1
fi
}
need GORELEASER_KEY
need GPG_SIGNING_KEY
need DOCKER_USERNAME
need DOCKER_PASSWORD
need CODESIGN_URL
need CODESIGN_CLIENT_CERT
need CODESIGN_CLIENT_KEY
need MACOS_SIGN_P12
need MACOS_SIGN_PASSWORD
need MACOS_NOTARY_KEY
need MACOS_NOTARY_KEY_ID
need MACOS_NOTARY_ISSUER_ID
need PACKAGECLOUD_TOKEN
need HOMEBREW_TAP_GITHUB_TOKEN
if [ "${missing}" -ne 0 ]; then
echo "refusing to publish with empty secrets" >&2
exit 1
fi
echo "required secrets are present"
+41 -1
View File
@@ -15,6 +15,10 @@ permissions:
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
id-token: write
env:
DOCKER_CLI_EXPERIMENTAL: "enabled"
DOCKER_REGISTRY: "ghcr.io"
@@ -24,6 +28,23 @@ jobs:
with:
fetch-depth: 0
- run: git fetch --force --tags
- name: Fail if publishing secrets are missing
env:
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
CODESIGN_URL: ${{ secrets.CODESIGN_URL }}
CODESIGN_CLIENT_CERT: ${{ secrets.CODESIGN_CLIENT_CERT }}
CODESIGN_CLIENT_KEY: ${{ secrets.CODESIGN_CLIENT_KEY }}
MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }}
MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }}
MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }}
MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}
PACKAGECLOUD_TOKEN: ${{ secrets.PACKAGECLOUD_TOKEN }}
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
run: bash .github/scripts/require_secrets.sh
- name: Install RPM tooling
run: sudo apt-get install -y rpm
- name: Set up QEMU
@@ -62,12 +83,21 @@ jobs:
# on your needs.
- name: "make key file"
run: "echo '${{ secrets.GPG_SIGNING_KEY }}' > /tmp/key.gpg"
- name: Install golift codesign
env:
GOBIN: ${{ runner.temp }}/codesign-bin
run: |
set -euo pipefail
mkdir -p "${GOBIN}"
go install golift.io/codesign/cmd/codesign@v1.0.3
echo "${GOBIN}" >> "${GITHUB_PATH}"
echo "CODESIGN_BIN=${GOBIN}/codesign" >> "${GITHUB_ENV}"
- uses: goreleaser/goreleaser-action@v7
with:
# either 'goreleaser' (default) or 'goreleaser-pro':
distribution: goreleaser-pro
version: latest
args: release --clean
args: release --clean --timeout 60m
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Your GoReleaser Pro key, if you are using the 'goreleaser-pro'
@@ -78,6 +108,16 @@ jobs:
PACKAGECLOUD_TOKEN: ${{ secrets.PACKAGECLOUD_TOKEN }}
GPG_SIGNING_KEY: /tmp/key.gpg
GPG_SIGNING_KEY_ID: ${{ steps.import_gpg.outputs.keyid }}
CODESIGN_URL: ${{ secrets.CODESIGN_URL }}
CODESIGN_CLIENT_CERT: ${{ secrets.CODESIGN_CLIENT_CERT }}
CODESIGN_CLIENT_KEY: ${{ secrets.CODESIGN_CLIENT_KEY }}
CODESIGN_NAME: unpoller
CODESIGN_WEBSITE: https://unpoller.com
MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }}
MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }}
MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }}
MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}
- name: Update Docker Hub Description
uses: peter-evans/dockerhub-description@v5
with:
+26
View File
@@ -74,6 +74,32 @@ builds:
- amd64
ldflags:
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser -X "golift.io/version.Version={{.Version}}" -X "golift.io/version.Branch={{.Branch}} ({{.Commit}})" -X "golift.io/version.BuildDate={{.Date}}" -X "golift.io/version.BuildUser=goreleaser" -X "golift.io/version.Revision=1"
hooks:
post:
- cmd: bash init/windows/signexe.sh "{{ .Path }}"
if: '{{ eq .Os "windows" }}'
universal_binaries:
- id: unpoller-mac
ids: [unpoller-mac]
replace: true
# Sign + notarize the macOS universal binary (quill; works on Linux CI).
# Enabled only when MACOS_SIGN_P12 is set so local snapshots still work.
notarize:
macos:
- enabled: '{{ isEnvSet "MACOS_SIGN_P12" }}'
ids:
- unpoller-mac
sign:
certificate: "{{ .Env.MACOS_SIGN_P12 }}"
password: "{{ .Env.MACOS_SIGN_PASSWORD }}"
notarize:
issuer_id: "{{ .Env.MACOS_NOTARY_ISSUER_ID }}"
key_id: "{{ .Env.MACOS_NOTARY_KEY_ID }}"
key: "{{ .Env.MACOS_NOTARY_KEY }}"
wait: true
timeout: 20m
archives:
- id: unpoller
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
set -e -o pipefail
# Authenticode-sign a Windows PE via golift/codesign (YubiKey-backed signerd).
# GoReleaser calls this from builds.hooks.post on windows binaries, after the
# CLI is installed into a temp GOBIN (never /usr/bin/codesign).
#
# Local snapshots skip when CODESIGN_URL is unset. GitHub Actions must fail
# closed — the release contract is an Authenticode-signed Windows binary.
#
# Prefer CODESIGN_BIN, then GOBIN/codesign, then GOPATH/bin, then PATH
# entries that are not Apple's /usr/bin/codesign.
function pick_codesign() {
if [ -n "${CODESIGN_BIN:-}" ]; then
echo "${CODESIGN_BIN}"
return
fi
if [ -n "${GOBIN:-}" ] && [ -x "${GOBIN}/codesign" ]; then
echo "${GOBIN}/codesign"
return
fi
gopath="$(go env GOPATH 2>/dev/null || true)"
if [ -n "${gopath}" ] && [ -x "${gopath}/bin/codesign" ]; then
echo "${gopath}/bin/codesign"
return
fi
while IFS= read -r p; do
case "$p" in
/usr/bin/codesign|/bin/codesign) continue ;;
esac
echo "$p"
return
done < <(type -a -p codesign 2>/dev/null || true)
return 1
}
function sign() {
if [ -z "${CODESIGN_URL:-}" ]; then
if [ -n "${GITHUB_ACTIONS:-}" ]; then
echo "CODESIGN_URL unset; refusing to ship an unsigned Windows binary in CI" >&2
exit 1
fi
echo "Skipped signing ${FILE} (CODESIGN_URL unset) .." >&2
exit 0
fi
bin="$(pick_codesign)" || {
echo "CODESIGN_URL is set but golift codesign CLI not found (set CODESIGN_BIN)" >&2
exit 1
}
CODESIGN_NAME="${CODESIGN_NAME:-unpoller}" \
CODESIGN_WEBSITE="${CODESIGN_WEBSITE:-https://unpoller.com}" \
"${bin}" -- "${FILE}"
echo "Signed ${FILE} .." >&2
}
[ -z "$1" ] || FILE="$1" sign