Self-hosted macOS CI Runner
Idea
Use the M1 MacBook Pro as a self-hosted GitHub Actions runner for iOS-specific CI jobs instead of paying for GitHub-hosted macOS runners.
Motivation
GitHub-hosted macOS runners cost 10x the Linux rate (0.008/min). With a 10x free-tier multiplier, the 2,000 free minutes effectively become 200 macOS minutes/month. At moderate usage (20 pushes/day), hosted macOS CI could cost ~0 and avoids cold-start VM spin-up time.
Plan
Only run macOS-required jobs on the self-hosted runner
rangeday bindings swift— UniFFI generationrangeday test ios— Swift/xcodebuild testsrangeday build ios— XCFramework cross-compilationrangeday check ios/rangeday fmt ios— SwiftLint/swift-format
Everything else (core, server, proto, Android) stays on cheap GitHub-hosted Linux runners.
Split CI workflow
jobs:
rust-and-proto:
runs-on: ubuntu-latest
steps:
- run: rangeday test core
- run: rangeday test server
- run: rangeday test proto
- run: rangeday check all
ios:
runs-on: self-hosted
needs: rust-and-proto # skip if Rust tests fail
steps:
- run: rangeday bindings swift
- run: rangeday test ios
- run: rangeday build ios- Path filtering — only trigger iOS job when
libs/core/orapps/ios/change. - Concurrency control — single machine, so cancel in-progress runs:
concurrency: group: ios-ci cancel-in-progress: true
Runner setup
# Download runner agent from repo Settings > Actions > Runners
./config.sh --url https://github.com/<org>/range-day --token <TOKEN>
# Install as launch daemon (survives reboots, runs with lid closed)
sudo ./svc.sh install
sudo ./svc.sh startLabel: self-hosted, macOS, ARM64
Gotchas
| Concern | Mitigation |
|---|---|
| Mac sleeping / lid closed | Launch daemon keeps runner alive; disable sleep in Energy settings |
| Xcode updates break builds | Pin Xcode version with xcode-select, update intentionally |
| Dirty state between runs | Runner uses fresh _work directory per job; cache Rust builds for speed |
| Mac offline / rebooting | GitHub queues the job and retries when runner reconnects |
| Security (public repos) | Only use self-hosted runners on private repos |
Open Questions
- Do we need the runner available 24/7 or only during working hours?
- Should we add a second Mac later for redundancy, or is one sufficient for MVP?