MacOn › Documentation
MacOn Documentation
Everything to turn your Mac into a local iOS CI runner — the app and the macon CLI.
Overview
MacOn is a local CI runner for iOS. It watches a Bitbucket or GitHub repository, checks out each new commit or pull request, runs the pipeline defined in your macon.yml, and reports the result back — all on a Mac you control.
It comes in two forms that share one engine:
- The app — a menu-bar/desktop control panel to configure pipelines, secrets, and watch state.
- The
maconCLI — the same runner, headless, installable via Homebrew. Ideal for scripts, servers, and always-on machines.
Anything you set up in the app can be exported and run by the CLI, and vice-versa.
Install
CLI (Homebrew)
App
Download the latest build from GitHub Releases. Requires macOS 14 or later.
First run: macon init
Before watching anything, confirm the machine has what an iOS build needs. macon init checks the toolchain and installs the Homebrew-managed pieces automatically.
It verifies Homebrew, Xcode + Command Line Tools, git, Ruby/Bundler, fastlane, SwiftLint, gitleaks, a JDK, cloudflared, and your installed simulator runtimes. Tools available via Homebrew are installed for you; Xcode and simulators print the exact command to run.
CLI overview
| Command | What it does |
|---|---|
macon init [--check] | Check the toolchain; install what's missing. |
macon sims … | List / install simulator runtimes & devices. |
macon lint [path] | Validate and summarize a macon.yml. |
macon run … | Run one workflow once, then exit. |
macon watch … | Watch a repo and build new commits (until Ctrl-C). |
macon service … | Run a watch as a background launchd service. |
macon companion … | Manage companion-app (iPhone/iPad) pairings. |
macon pipelines [file] | List pipelines in an app export file. |
Simulators
Inspect and manage the simulators your test matrix targets — for iOS, watchOS, tvOS, and visionOS (macOS builds run natively, no simulator).
Use macon sims to find the exact device and os values for a test matrix.
Run once
Run a single workflow in a checkout and exit with its status code — perfect for a git hook, cron, or another CI.
Watch
Continuously build new commits (or pull requests). Runs until you stop it.
Options
| Flag | Meaning |
|---|---|
--provider bitbucket|github | Git host (default bitbucket). GitHub: workspace = owner/org. |
--branch B / --prs | Watch a branch, or open PRs (--pr-target to filter). |
--webhook [--port N] | Push mode — build the instant the host calls you. |
--webhook-secret S | Require a secret (GitHub HMAC, or in the URL path). |
--every SECS | Poll interval for polling mode (default 30). |
--timeout MINS | Cancel a build that runs longer than this. |
--workflow N / --file | Which workflow / which pipeline file. |
--no-status | Don't post build status back to the host. |
--companion [--companion-port N] | Serve the iPhone/iPad app (default port 8899). |
Run as a service
Install a watch as a launchd agent so it starts at login and restarts on crash — the way to run MacOn unattended on a dedicated or cloud (EC2) Mac.
Credentials present in your shell at install time (BITBUCKET_*, GITHUB_TOKEN, ASC_*, SLACK_URL) are copied into the agent. Logs go to ~/Library/Logs/macon/<label>.log.
macon.yml — structure
A pipeline lives in your repo as macon.yml. It's Bitrise-style: named workflows composed with before_run/after_run, triggers that route branches/PRs to workflows, environment variables, and conditional or always-run steps.
Workflows
Each workflow is a named list of steps. Compose them so shared setup runs once:
before_run: [_setup]— run these workflows' steps first.after_run: [_notify]— run these afterward (e.g. Slack).env:— variables scoped to the workflow.
Pick which workflow runs with --workflow, or leave it to the triggers.
Steps
A step is a name and a shell script. Each becomes its own timed section in the app's log.
| Field | Meaning |
|---|---|
name | Shown in logs. |
script | Shell to run (zsh, in the checkout). |
run_if | Only run if this condition exits 0. e.g. '[ -n "$MACON_PR_ID" ]' |
always_run | true to run even if an earlier step failed. |
matrix | Fan the step out over combinations — see below. |
Test matrix
Fan a step out across every combination of values — ideal for UI-testing on multiple devices and OS versions. Each run gets MACON_MATRIX_<KEY> in its environment. All combinations run; the step fails if any of them fails.
Combinations run sequentially so simulators don't contend. Match the values to installed runtimes (macon sims).
Triggers
Route branches and pull requests to workflows (like Bitrise's trigger map). When a pipeline's workflow field is blank, MacOn matches here.
Built-in variables
Every step runs with these set:
| Variable | Value |
|---|---|
MACON_COMMIT, MACON_COMMIT_SHORT | The commit being built. |
MACON_BRANCH | Branch (or PR source branch). |
MACON_REPO | workspace/repo. |
MACON_WORKFLOW | The running workflow's name. |
MACON_BUILD_NUMBER | Incrementing build count. |
MACON_PR_ID, MACON_PR_TITLE, … | Set on pull-request builds. |
MACON_MATRIX_* | Set inside a matrix step. |
Provider-native variables (BITBUCKET_* or GITHUB_*) are also injected so tools like Danger work.
Bitbucket & GitHub
A pipeline watches one host. Set credentials once (in the app's Settings, or via environment for the CLI).
| Provider | Auth | Environment |
|---|---|---|
| Bitbucket | Email + API token | BITBUCKET_EMAIL, BITBUCKET_API_TOKEN |
| GitHub | Personal Access Token | GITHUB_TOKEN |
The GitHub token needs repo access — classic repo scope, or fine-grained with Contents + Commit statuses + Pull requests.
Secrets
Secrets your pipeline needs (ASC_KEY_ID, ASC_ISSUER_ID, ASC_KEY_CONTENT, SLACK_URL, …) are injected into every step. In the app they're stored in the macOS Keychain, never in the repo. For the CLI they come from the inherited shell environment — unless you exported the config with secrets.
Shipping to TestFlight
MacOn runs your fastlane lanes, so shipping is a normal beta lane with automatic signing and an App Store Connect API key.
- Create an API key in App Store Connect → Users and Access → Integrations. Download the
.p8once. - Provide it as three secrets:
ASC_KEY_ID,ASC_ISSUER_ID, andASC_KEY_CONTENT(the.p8, base64-encoded). - Route
mainto a workflow that runsfastlane beta.
App ↔ CLI export
Set everything up visually in the app, then run it headless. In the app: Settings → Export Configuration… writes a macon-export.json (optionally including secrets). Then:
Exported without secrets, the file is config-only and safe to commit; supply token/secret values via the environment. Exported with secrets, it's self-contained — keep it private. The app's Import… loads a file back, so it also moves a setup between machines.
Companion app (iPhone & iPad)
The MacOn companion app lets you monitor builds and tail logs live from your phone or iPad — handy when the runner is a Mac in another room or a headless cloud instance. Add --companion to any watch to serve it:
The server is lightweight and read-only — the app can watch, but not start or change builds. It rides alongside the watch, so it sees the same runs you'd see in the desktop app.
Prefer the desktop app? Flip it on in Settings → Companion app, then Pair a device shows the address, code, and a QR. Pairings are shared with the CLI, so a device works with either.
Pairing & devices
On start, the companion server prints a one-time address and code. In the app, tap Add runner and enter them — the code is exchanged for a long-lived device token stored in the iOS Keychain, so you only pair once. Codes are single-use, expire (15 min by default), and are rate-limited.
Headless or cloud (EC2) Macs have no screen to read — the code prints to the log, so grab it over SSH. Expose the port through a cloudflared tunnel and point the app at the tunnel host (the app talks HTTPS/WSS):
Manage paired devices from the CLI — file-based, so it works even with no server running: