MacOn › Documentation

MacOn Documentation

Everything to turn your Mac into a local iOS CI runner — the app and the macon CLI.

Prefer the raw docs? The same reference lives on GitHub. View on GitHub ↗

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:

Anything you set up in the app can be exported and run by the CLI, and vice-versa.

Install

CLI (Homebrew)

brew tap alimusawa313/macon https://github.com/alimusawa313/homebrew-macon brew install macon

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.

macon init # check + install what's missing macon init --check # report only, install nothing

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

CommandWhat 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).

macon sims # list runtimes + device types macon sims install 18.1 # iOS runtime (bare version ⇒ iOS) macon sims install watchOS 11.2 # any platform + version macon sims create "iPhone 16" 18.1 # create a specific device

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.

macon run --workflow test # in the current directory macon run --workflow beta ~/src/planpal # in a given repo # secrets come from the shell environment ASC_KEY_ID=… ASC_ISSUER_ID=… ASC_KEY_CONTENT=… macon run --workflow beta

Watch

Continuously build new commits (or pull requests). Runs until you stop it.

# poll a branch on Bitbucket macon watch --workspace acme --repo app --branch main # open PRs on GitHub, instant via webhook macon watch --provider github --workspace org --repo app \ --prs --webhook --port 8787 --webhook-secret "$SECRET" # everything you set up in the app macon watch --config macon-export.json

Options

FlagMeaning
--provider bitbucket|githubGit host (default bitbucket). GitHub: workspace = owner/org.
--branch B / --prsWatch a branch, or open PRs (--pr-target to filter).
--webhook [--port N]Push mode — build the instant the host calls you.
--webhook-secret SRequire a secret (GitHub HMAC, or in the URL path).
--every SECSPoll interval for polling mode (default 30).
--timeout MINSCancel a build that runs longer than this.
--workflow N / --fileWhich workflow / which pipeline file.
--no-statusDon'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.

macon service install --config ~/macon-export.json macon service install --provider github --workspace org --repo app --webhook --label ci macon service status [--label NAME] macon service uninstall [--label NAME]

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.

name: PlanPal iOS CI workflows: _setup: steps: - { name: Gems, script: bundle install } - { name: SwiftLint, script: swiftlint lint --strict } test: before_run: [_setup] steps: - name: UI Tests script: bundle exec fastlane test beta: before_run: [test] steps: - { name: TestFlight, script: bundle exec fastlane beta } triggers: - { pull_request: "*", workflow: test } - { branch: main, workflow: beta }

Workflows

Each workflow is a named list of steps. Compose them so shared setup runs once:

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.

FieldMeaning
nameShown in logs.
scriptShell to run (zsh, in the checkout).
run_ifOnly run if this condition exits 0. e.g. '[ -n "$MACON_PR_ID" ]'
always_runtrue to run even if an earlier step failed.
matrixFan 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.

- name: UI Tests matrix: device: ["iPhone 17 Pro", "iPad Air 11-inch (M4)"] os: ["26.4", "26.5"] # → 4 runs script: bundle exec fastlane test device:"$MACON_MATRIX_DEVICE" os:"$MACON_MATRIX_OS"

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.

triggers: - { pull_request: "*", workflow: test } # any PR - { branch: main, workflow: beta } - { branch: "dev-*", workflow: test } # globs supported

Built-in variables

Every step runs with these set:

VariableValue
MACON_COMMIT, MACON_COMMIT_SHORTThe commit being built.
MACON_BRANCHBranch (or PR source branch).
MACON_REPOworkspace/repo.
MACON_WORKFLOWThe running workflow's name.
MACON_BUILD_NUMBERIncrementing 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).

ProviderAuthEnvironment
BitbucketEmail + API tokenBITBUCKET_EMAIL, BITBUCKET_API_TOKEN
GitHubPersonal Access TokenGITHUB_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.

  1. Create an API key in App Store Connect → Users and Access → Integrations. Download the .p8 once.
  2. Provide it as three secrets: ASC_KEY_ID, ASC_ISSUER_ID, and ASC_KEY_CONTENT (the .p8, base64-encoded).
  3. Route main to a workflow that runs fastlane beta.
# base64 of the .p8, straight to your clipboard base64 -i ~/Downloads/AuthKey_XXXX.p8 | pbcopy

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:

macon pipelines macon-export.json # see what's inside macon watch --config macon-export.json # run them all macon service install --config macon-export.json # …as a service

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:

macon watch --workspace acme --repo app --branch main --companion macon watch --config macon-export.json --companion # with your app setup

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.

The app is a separate open-source project — pair it once and it reconnects on its own. Companion on GitHub ↗

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.

┌─ Pair the MacOn companion app ────────────────────── │ Address: alis-mac.local:8899 │ Code: K7QP-2M9X-4RTD · valid 15 min, one device └──────────────────────────────────────────────────────

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):

cloudflared tunnel --url http://localhost:8899 # → https://<name>.trycloudflare.com

Manage paired devices from the CLI — file-based, so it works even with no server running:

macon companion devices # list paired iPhones/iPads macon companion revoke <prefix> # revoke one (token prefix from `devices`) macon companion revoke-all # revoke everything
On the roadmap: the app monitors builds and streams logs today. Streaming the Mac's screen (watch the simulator run) is a planned later phase.
That's the full picture. For the exhaustive flag-by-flag reference, see the CLI.md on GitHub ↗.