Skip to content

chore(release): pin signing to self-hosted Mac runner, drop repo secrets#6

Merged
momenbasel merged 4 commits into
mainfrom
chore/release-self-hosted
May 14, 2026
Merged

chore(release): pin signing to self-hosted Mac runner, drop repo secrets#6
momenbasel merged 4 commits into
mainfrom
chore/release-self-hosted

Conversation

@momenbasel

Copy link
Copy Markdown
Owner

Why

Repo is public. Storing the Apple Developer ID cert / notary password / tap PAT in GitHub Actions secrets puts them one compromised workflow run away from leaking. Move the signing-sensitive jobs to a self-hosted Mac runner so those credentials never enter GitHub.

Changes

  • release.yml macos-pkg + bump-tap jobs now runs-on: [self-hosted, macOS, timenest-release].
  • Runner reads three values from its local .env (none of which are repo secrets):
    • TIMENEST_SIGN_IDENTITY - Developer ID Installer identity string
    • TIMENEST_NOTARY_PROFILE - notarytool keychain profile name
    • TIMENEST_TAP_TOKEN - fine-grained PAT scoped to momenbasel/homebrew-timenest
  • Notarytool reads the app-specific password from the keychain profile (created once with xcrun notarytool store-credentials); no password ever touches a file.
  • github-hosted jobs (tarball-hash, publish-release, all image builds + cosign signing) stay on ubuntu-latest/macos-14 and never touch sensitive material.
  • docs/release-pipeline.md rewritten with the new bootstrap procedure (cert request flow, notary profile setup, PAT generation, runner env wire-up) plus verification commands.

State on the user's Mac (already wired)

  • Tap repo created: https://github.com/momenbasel/homebrew-timenest (initial Formula seeded)
  • Runner registered + service-installed: Moamens-MacBook-Pro-timenest (labels self-hosted,macOS,ARM64,timenest-release, status online)
  • .env placeholders written under ~/actions-runner-timenest/.env

What's still missing before cutting v0.2.0

  • Developer ID Installer cert (Developer ID Application exists but is for .app codesign, not .pkg). Request the Installer variant at developer.apple.com.
  • xcrun notarytool store-credentials timenest-notary ... once the app-specific password exists.
  • TIMENEST_TAP_TOKEN fine-grained PAT pasted into ~/actions-runner-timenest/.env.

Test plan

  • Self-hosted runner online + labelled correctly
  • Tap repo public + seeded
  • CI green on this PR
  • After the three pending items above: tag v0.0.2-test, watch release.yml end-to-end, verify pkgutil --check-signature + cosign verify + tap bump commit

The repo is public; storing the Apple Developer ID cert, notary
app-specific password, or a homebrew-tap PAT in GitHub Actions secrets
puts them one compromised workflow_run away from leaking. Move the
signing-sensitive jobs (macos-pkg, bump-tap) to a self-hosted Mac
runner that holds the cert in its login keychain and the PAT in its
.env. The github-hosted jobs (image build + cosign, tarball-hash,
publish-release) never touch the sensitive material.

The runner expects three values in its .env, none of which are repo
secrets:
- TIMENEST_SIGN_IDENTITY  (Developer ID Installer identity string)
- TIMENEST_NOTARY_PROFILE (notarytool keychain profile name)
- TIMENEST_TAP_TOKEN      (PAT scoped to momenbasel/homebrew-timenest)

cosign keylessly signs every image via the workflow's OIDC token, so
the docker pipeline needs no secret either.

docs/release-pipeline.md updated end-to-end: bootstrap procedure,
runner status checks, release ritual, verification commands.
Copilot AI review requested due to automatic review settings May 14, 2026 22:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Moves macOS package signing/notarization and Homebrew tap bumping off GitHub-hosted runners and onto a labeled self-hosted macOS runner, eliminating the need to store Apple signing/notary credentials and the tap PAT in GitHub Actions secrets.

Changes:

  • Update release.yml to run macos-pkg and bump-tap on a self-hosted macOS runner and use keychain-based signing/notary credentials.
  • Remove reliance on repository secrets for Apple cert import/keychain setup and tap token usage.
  • Rewrite docs/release-pipeline.md to document the new self-hosted bootstrap and verification steps.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
docs/release-pipeline.md Updates release/bootstrap documentation to reflect self-hosted signing and keychain profile setup.
.github/workflows/release.yml Pins signing + tap bump jobs to a labeled self-hosted macOS runner and removes secret-based credential handling.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +55 to 62
- name: Codesign .pkg (identity from local keychain)
env:
SIGN_IDENTITY: ${{ secrets.APPLE_DEVELOPER_ID_INSTALLER_IDENTITY }}
# SIGN_IDENTITY is set in the runner's environment (e.g. the
# service plist or `~/.timenest-runner.env`), NOT in repo
# secrets. Example value:
# Developer ID Installer: Greycore Labs (TEAMID12)
SIGN_IDENTITY: ${{ env.TIMENEST_SIGN_IDENTITY }}
VERSION: ${{ steps.ver.outputs.version }}
Comment on lines +72 to 80
- name: Notarize + staple (keychain profile)
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_NOTARY_PASSWORD: ${{ secrets.APPLE_NOTARY_PASSWORD }}
# TIMENEST_NOTARY_PROFILE is the name of the keychain profile
# created on the runner with:
# xcrun notarytool store-credentials timenest-notary \
# --apple-id <APPLE_ID> --team-id <TEAM_ID> --password <APP_PASSWORD>
# The actual credentials never leave the keychain.
NOTARY_PROFILE: ${{ env.TIMENEST_NOTARY_PROFILE }}
VERSION: ${{ steps.ver.outputs.version }}
Comment thread .github/workflows/release.yml Outdated
Comment on lines +194 to +200
env:
TAP_TOKEN: ${{ env.TIMENEST_TAP_TOKEN }}
run: |
set -euo pipefail
: "${TAP_TOKEN:?TIMENEST_TAP_TOKEN not set on the runner}"
rm -rf tap
git clone "https://x-access-token:${TAP_TOKEN}@github.com/momenbasel/homebrew-timenest.git" tap
Comment thread docs/release-pipeline.md
Comment on lines +70 to +84
### 4. Wire runner env

Edit `~/actions-runner-timenest/.env`:

```env
TIMENEST_SIGN_IDENTITY=Developer ID Installer: Moamen Basel (H3WXHVTP97)
TIMENEST_NOTARY_PROFILE=timenest-notary
TIMENEST_TAP_TOKEN=github_pat_...
```

Then restart the runner service so it picks up the new env:

```bash
cd ~/actions-runner-timenest && ./svc.sh stop && ./svc.sh start
```
- Add Sorbet sigil + frozen_string_literal pragma, move version above
  sha256, swap deprecated assert_predicate for assert_path_exists.
  Clears every brew style nit, leaving only the Sorbet "false vs
  strict" advisory that mainline Homebrew taps also keep at false.
- Sync the same formula into the tap repo.
- Rewrite docs/release-pipeline.md to reflect the actual state of this
  Mac: tap created, runner installed + online, AC_NOTARY profile
  reused, CSR pre-generated. Note the one remaining manual step (Apple
  Developer ID Installer cert request) is unavoidable because Apple's
  App Store Connect API only exposes the Application and Kext variants
  of Developer ID, not Installer.
The bump-tap job is already self-hosted, so it can authenticate against
github.com with `gh auth token` from the runner's logged-in CLI session
(scopes include `repo`). This eliminates the last manual credential
the maintainer had to provision: no PAT to generate, no .env var to
rotate, no extra secret to keep in sync.

If the runner ever loses the repo scope, `gh auth refresh -s repo`
restores it without touching this workflow.
@momenbasel momenbasel force-pushed the chore/release-self-hosted branch from 5ca68c2 to c075eac Compare May 14, 2026 22:32
Single command tells the maintainer exactly what's wired up and what's
blocking a release: tap repo, runner status, both Apple cert variants,
notarytool keychain profile, runner .env, gh CLI auth, and cosign
signatures on the currently published images. Exits 0 only when every
prerequisite is met.
@momenbasel momenbasel merged commit 18c3f67 into main May 14, 2026
6 checks passed
@momenbasel momenbasel deleted the chore/release-self-hosted branch May 14, 2026 22:34
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.

2 participants