Skip to content

ci: add macOS arm64 runners to the wavekat-ci pool - #158

Merged
wavekat-eason merged 2 commits into
mainfrom
claude/github-actions-arm64-q4wiac
Aug 16, 2026
Merged

ci: add macOS arm64 runners to the wavekat-ci pool#158
wavekat-eason merged 2 commits into
mainfrom
claude/github-actions-arm64-q4wiac

Conversation

@wavekat-eason

Copy link
Copy Markdown
Contributor

Adds macOS (Apple Silicon) support to the self-hosted GitHub Actions runner pool, allowing CI jobs to run on either the existing Linux x86-64 workstation or a new Mac mini, both carrying the wavekat-ci label.

Changes

  • scripts/setup-gha-runners-macos.sh: New script to install and register N launchd-based GitHub Actions runners on macOS. Mirrors the Linux setup script but uses actions-runner-osx-arm64, launchd LaunchAgents instead of systemd, and writes a .path file so Homebrew binaries are visible to the runner (launchd does not source shell profiles). Includes headless-Mac guidance (automatic login + sleep prevention).

  • scripts/uninstall-gha-runners-macos.sh: Companion teardown script that stops launchd agents, de-registers runners from the org, and cleans up.

  • docs/06-self-hosted-runners.md: Comprehensive guide covering:

    • How the mixed-pool label system works (any runner with all requested labels can pick up a job)
    • Step-by-step Mac mini setup with Xcode, Homebrew, and gh CLI
    • Platform-specific differences (launchd vs systemd, .path file, quarantine clearing)
    • The headless-Mac gotcha and its fix (automatic login + RUNNER_KEEP_AWAKE=1)
    • Critical rule: every run: block must be portable across BSD and GNU userland — documents the traps (no grep -P, sed -i syntax, readlink -f, date -d, sha256sum, xargs -r, bash 3.2 limits) with POSIX-safe alternatives
    • Operating notes (status, logs, the persistent _work directory)
  • .github/workflows/preview.yml: Fixed the Cloudflare deployment alias extraction to use POSIX sed instead of grep -oP, with an inline comment explaining the portability requirement. This was the only workflow change needed — the job now works on both Linux and macOS runners.

  • CLAUDE.md: Added a section documenting the mixed-pool constraint and pointing to the full guide.

Implementation notes

The macOS script handles several platform-specific details:

  • Detects Apple Silicon (arm64) vs Intel (x64) and fetches the correct runner binary
  • Validates git is usable (fresh macOS ships a stub that prompts for Command Line Tools)
  • Resolves the latest runner version from GitHub API if not pinned
  • Clears the com.apple.quarantine attribute from curl-fetched tarballs (no-op in normal path, but prevents Gatekeeper from killing binaries)
  • Detects Homebrew location (/opt/homebrew for Apple Silicon, /usr/local for Intel) and writes it into the runner's .path so launchd-spawned processes can find it
  • Gracefully handles launchctl failures on headless Macs with actionable guidance

The portability fix in preview.yml uses sed -nE (extended regex, POSIX) with a character class [[:space:]] instead of \S, and pipes through tail -n 1 to handle multi-line output safely.

https://claude.ai/code/session_01TxidDNZBX6a7YqiPdmjf6C

`runs-on: [self-hosted, wavekat-ci]` is a label pool, not a machine, so
adding the Mac mini is a registration problem rather than a workflow one.
Add the macOS twin of the Linux setup/uninstall scripts, registering with
the same `wavekat-ci` label so jobs land on whichever host is idle.

macOS forces three differences from the Linux script: the osx-arm64 runner
package, a launchd LaunchAgent instead of a systemd unit (so runners live
under $HOME and need no sudo), and a per-runner `.path` file, since launchd
does not source the shell profile and Homebrew would otherwise be invisible.

A mixed pool also means any job can land on BSD userland, so `preview.yml`
loses its `grep -oP` — BSD grep has no PCRE, and that step would have failed
every time the deploy job landed on the Mac. `docs/06` records the rest of
the portability traps and the headless-Mac requirements (auto-login, no
sleep) that keep the agents online across reboots.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TxidDNZBX6a7YqiPdmjf6C
@wavekat-eason wavekat-eason changed the title Add macOS self-hosted runner setup and mixed-pool CI docs ci: add macOS arm64 runners to the wavekat-ci pool Aug 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Switches the macOS runners from a native launchd install to Docker
Desktop containers, reusing the existing scripts/docker image.

Reading the other repos on this label is what settles it: wavekat-voice's
ci.yml runs `sudo apt-get install` three times, so every wavekat-ci job in
that repo would have failed the moment it landed on a native macOS runner.
macOS cannot run macOS containers, so Docker Desktop means the Mac
contributes Linux/arm64 capacity — which is the goal, since nothing in the
pool's workload needs macOS and a uniform Ubuntu userland removes the
BSD-vs-GNU failure mode entirely. The image already resolved its arch via
dpkg, so it builds natively on Apple Silicon unchanged.

Supervision is Docker's own --restart unless-stopped rather than launchd;
the reboot story is Docker Desktop's start-at-login plus auto-login.

docs/06 now also records who else rides on this label — seven repos, with
wavekat-voice and wavekat-asr building sherpa-onnx native code and so
arch-sensitive. No shipped artifact is built on wavekat-ci (installers use
GitHub-hosted runners), so a bad host means red CI, not a bad release.
Recommends registering the Mac under wavekat-ci-arm64 first and opting
repos in, rather than widening the label blind.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TxidDNZBX6a7YqiPdmjf6C
@wavekat-eason
wavekat-eason merged commit 7af2f9e into main Aug 16, 2026
2 checks passed
@wavekat-eason
wavekat-eason deleted the claude/github-actions-arm64-q4wiac branch August 16, 2026 07:01
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