ios-selfhosted-ci
Health Uyari
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 5 GitHub stars
Code Basarisiz
- rm -rf — Recursive force deletion command in check.sh
- rm -rf — Recursive force deletion command in lib.sh
- rm -rf — Recursive force deletion command in review/action.yml
Permissions Gecti
- Permissions — No dangerous permissions requested
Bu listing icin henuz AI raporu yok.
Turn your Mac into CI for all your iOS apps: PR checks, TestFlight builds of any branch from your phone, and Claude code review — managed from one repo.
ios-selfhosted-ci
Turn the Mac on your desk into the CI for all your iOS apps — PR checks with unit tests, TestFlight builds of any branch from your phone, and AI code review, managed from one repo.
This project is the infrastructure layer: self-hosted runners, reusable workflows, keychain isolation, signing (cloud or manual). For everything App Store Connect it pairs with asc; for marketing screenshots with app-store-screenshots.
| Layer | Tool |
|---|---|
| Runners, PR check + tests, any-branch TestFlight, signing, one-command repo setup | this repo |
| AI PR review | Claude Code, Codex or Gemini CLI on your Mac (this repo), one prompt, inline comments |
| Submissions, metadata, TestFlight groups, review status | asc |
| Marketing screenshots | app-store-screenshots → asc screenshots upload |
Why
For a solo developer with a handful of private iOS apps:
| Monthly cost (~12 apps) | AI review | GitHub required checks | Any branch from phone | |
|---|---|---|---|---|
| This (your own Mac) | ~$0 | ✅ Claude, on your subscription | ✅ | ✅ |
| Xcode Cloud | 25 h free, then $49.99+ | ❌ | ❌ | ✅ |
| Codemagic / Bitrise | ~$50+ | ❌ | ✅ | ✅ |
| GitHub-hosted macOS runners | ~$60+ | ✅ | ✅ | ✅ |
What you get:
- PR check — every PR runs the unit tests on your Mac (UI tests skipped; build-only when there are no tests, or an unsigned device build). Enforced with a ruleset.
- TestFlight from anywhere —
Actions › TestFlight › Run workflow, type any branch/tag/commit (it doesn't even need to contain the workflow). Build number defaults toYYMMDD01and Xcode bumps it on clashes. - AI review — each PR gets inline comments + a summary from Claude, Codex or Gemini (whichever you use), through its CLI on a dedicated runner so it never blocks builds.
- No certificates to juggle — Xcode cloud signing with an App Store Connect API key; no p12/profiles in secrets, no fastlane match. (Already on fastlane? See docs/fastlane.md.)
- One command per new app —
scripts/bootstrap-repo.sh MyAppsets up runners, workflows, ruleset and verifies a build. - Change once, apply everywhere — app repos only call the reusable workflows; edit prompts, models or steps here.
⚠️ Private repos only. A self-hosted runner on a public repo lets anyone's pull request run code on your Mac. The scripts refuse public repos. Read docs/security.md before you start.
Recommended setup
| Recommendation | |
|---|---|
| Machine | Apple silicon Mac mini, 16 GB+ RAM (24 GB+ if it also runs VMs/containers), 256 GB+ free SSD space |
| macOS user | Ideally a separate standard user just for CI (see security). Auto-login + never sleep. |
| Power | System Settings › Energy: prevent sleep, start after power failure |
| Xcode | Release Xcode at /Applications/Xcode.app. Keep betas elsewhere and only select them explicitly. |
| Tools | GitHub CLI (gh auth login), Claude Code (claude), bun (makes reviews start fast), asc (brew install asc) |
| GitHub | Rulesets on private repos need GitHub Pro (personal) or Team (org). Everything else works on Free. |
| Network | Wired Ethernet. Point the review workflow at locally installed claude/bun so jobs don't re-download them. |
Who can set this up
App Store Connect (the API key lives on the runner Mac):
| Step | Who | Notes |
|---|---|---|
| Enable the App Store Connect API for the team (first time only) | Account Holder | The "Request Access" button on Users and Access › Integrations |
| Create the team API key | Admin (or Account Holder) | Users with other roles can't open the Team Keys page |
| Key role used by this project | Admin | Verified for everything here: archive with automatic signing, cloud-managed distribution signing, TestFlight upload, submission with asc |
| Key role for asc alone (versions, What's New, submit) | App Manager should be enough | Not verified here; replying to customer reviews needs Admin |
| Individual (personal) API keys | ❌ not supported | Apple: individual keys can't use the Provisioning endpoints (needed for signing), and the scripts expect an Issuer ID |
Cloud-managed distribution signing is available by default to Account Holder and Admin; Developers need the
Access to Cloud Managed Distribution Certificate permission. Whether an App Manager key can cloud-sign
isn't documented — use Admin unless you've tested otherwise.
If the app belongs to a team where you're only App Manager or Developer (typical for a company account), use
manual signing: an Apple Distribution certificate + App Store profiles from the
team's Admin (or exported from the Mac you already publish from), and an App Manager key or your Apple ID +
app-specific password for uploads — no Admin key needed. Otherwise you can still use the PR check and Claude
review (--no-testflight).
GitHub: you need admin rights on the app repos (runners, secrets, rulesets). Rulesets on private repos
need GitHub Pro (personal accounts) or Team (organizations).
The Mac: the runner user's login keychain must hold an Apple Development identity for the team (Xcode ›
Settings › Accounts creates it); ci-signing-setup.sh keychain copies it into ci.keychain.
Sources: Creating API keys ·
Cloud-managed certificates ·
Program roles
Setup (about 20 minutes)
1. Use this repo
Either reference it directly (puyanlin/ios-selfhosted-ci@v1) or fork it (recommended: you control updates). If you fork, replace puyanlin/ios-selfhosted-ci with your fork in .github/workflows/*.yml and set CI_REPO below. For a private fork, allow access: fork › Settings › Actions › General › Access → "Accessible from repositories owned by the user".
git clone https://github.com/puyanlin/ios-selfhosted-ci ~/ios-selfhosted-ci
mkdir -p ~/.config/ios-selfhosted-ci
cp ~/ios-selfhosted-ci/config.example ~/.config/ios-selfhosted-ci/config
open -e ~/.config/ios-selfhosted-ci/config # GH_OWNER, TEAM_ID, CI_REPO …
2. Signing (once per Mac)
- Signing identity — make sure Xcode has created your Apple Development certificate (Xcode › Settings › Accounts). Then:
This createsscripts/ci-signing-setup.sh keychain # macOS asks for your login password onceci.keychainholding only your signing identities. Jobs unlock this keychain and never see your login keychain. - App Store Connect API key — App Store Connect › Users and Access › Integrations › App Store Connect API › Team Keys › +, access Admin (needed for cloud-managed distribution signing). Download the
.p8(only once!) and note the Issuer ID:scripts/ci-signing-setup.sh asc ~/Downloads/AuthKey_XXXXXXXXXX.p8 <issuer-id> scripts/ci-signing-setup.sh status
3. Each app repo
scripts/bootstrap-repo.sh MyApp
# two apps in one repo on different branches:
scripts/bootstrap-repo.sh MyApp --scheme "MyApp MyAppPro" --branch pro
# an SDK without an arm64 simulator slice:
scripts/bootstrap-repo.sh MyApp --destination device
# review comments in another language (or set REVIEW_LANGUAGE in the config):
scripts/bootstrap-repo.sh MyApp --review-language "Traditional Chinese"
# PR check + review only, no TestFlight:
scripts/bootstrap-repo.sh MyApp --no-testflight
# no ruleset yet (the default branch does not build yet):
scripts/bootstrap-repo.sh MyApp --no-ruleset
# PR check builds only (tests are broken for now) / skip some tests:
scripts/bootstrap-repo.sh MyApp --no-test
scripts/bootstrap-repo.sh MyApp --skip-testing "MyAppTests/SlowTests"
Re-running it on a repo that is already set up is safe: it only updates changed caller files and never duplicates the ruleset. Set XCODE_BETA_APP in the config to offer a beta Xcode in the TestFlight menu.
Using Claude Code?
ln -s ~/ios-selfhosted-ci/skills/ios-ci-setup ~/.claude/skills/ios-ci-setupand just ask it to "set up CI for MyApp" — the ios-ci-setup skill runs this for you.
4. Claude review token (once, plus once per new repo)
In your own terminal (not through an AI agent, so the token never lands in a transcript):
claude setup-token # copy the sk-ant-oat… token
scripts/set-claude-token.sh # every repo with a runner; or: set-claude-token.sh MyApp
Personal GitHub accounts have no account-wide Actions secrets, so each repo needs the secret.
Daily use
- Open a PR →
pr-check / buildandreview / reviewstart in parallel. - Ship a build → Actions › TestFlight › Run workflow: branch (any ref), upload on/off, Xcode, build number. The GitHub mobile app works.
gh workflow run testflight.yml -R you/MyApp -f branch=feature/x # from a terminal gh workflow run testflight.yml -R you/MyApp -f branch=main -f upload=false # dry run - Change the pipeline → edit
.github/workflows/*here, thengit tag -f v1 && git push -f origin v1. Every app picks it up on its next run. (Pin callers to a commit SHA instead ofv1if you prefer immutable versions.)
Releasing to the App Store (with asc)
This project doesn't talk to the App Store Connect API itself — use asc (App Store Connect CLI, MIT),
which covers submissions, metadata, screenshots, TestFlight groups, review status and more. It uses the same API key:
brew install asc
asc telemetry disable # optional: it sends anonymous usage stats by default
asc install-skills # optional: 25 agent skills (asc-release-flow, asc-whats-new-writer, …)
scripts/ci-signing-setup.sh asc … # installs the key for the runner AND registers it with asc
A typical release, after the TestFlight workflow uploaded the build:
asc release stage --app APP_ID --version 1.4.0 --build BUILD_ID --metadata-dir ./metadata/version/1.4.0 --dry-run
asc release stage --app APP_ID --version 1.4.0 --build BUILD_ID --metadata-dir ./metadata/version/1.4.0 --confirm
asc validate --app APP_ID --version 1.4.0 --platform IOS
asc review submit --app APP_ID --version 1.4.0 --build BUILD_ID --confirm
asc review status --app APP_ID
Only the API key is needed — no Apple ID password. (asc web auth login is optional, for extra checks Apple exposes
only on the website.) For marketing screenshots, app-store-screenshots
designs them and asc screenshots upload uploads them.
AI review: pick a provider
Every repo's .github/workflows/ai-review.yml sets provider: — use whichever service the runner's owner already
pays for. All three run on your Mac through their own CLI, share one review prompt (review/prompt.md) and one
output format, and post the same way: a PR review with inline comments on the affected lines plus a summary.
provider |
CLI on the runner | Sign-in (pick one) |
|---|---|---|
claude (default) |
claude |
secret CLAUDE_CODE_OAUTH_TOKEN (claude setup-token → scripts/set-claude-token.sh) |
codex |
codex (npm i -g @openai/codex) |
codex login on the runner (ChatGPT sign-in), or secret OPENAI_API_KEY |
gemini |
gemini (npm i -g @google/gemini-cli) |
sign in once by running gemini on the runner, or secret GEMINI_API_KEY |
Each job copies only the credentials into a fresh config folder, so your own settings, plugins and MCP servers
never load in CI. Set the default for new repos with REVIEW_PROVIDER in the config orbootstrap-repo.sh MyApp --review-provider codex; override the model with model:. runner: '"ubuntu-latest"'
also works (the CLI is installed on the fly; an API-key secret is then required). Codex and Gemini are less
battle-tested than Claude here.
Choosing the Xcode version
Every workflow resolves Xcode the same way (see xcode.sh):
- the
xcodeinput — a path (/Applications/Xcode-beta.app) or a version (27.1, or27= newest installed 27.x, release builds preferred over betas); - otherwise the repo's
.xcode-versionfile (the xcodes/fastlane convention, e.g.27.1); - otherwise the runner default (
/Applications/Xcode.app).
Install several Xcodes side by side (e.g. with xcodes) and pin per repo with .xcode-version.
Reusable workflow inputs
| Workflow | Input | Default | Notes |
|---|---|---|---|
| pr-check | scheme |
— | Several space-separated candidates allowed |
project |
auto | .xcodeproj/.xcworkspace at the root |
|
destination |
simulator |
device = unsigned device build |
|
test |
true |
Unit tests on an iPhone simulator (ad-hoc signed test host); falls back to a build when the scheme has no tests | |
skip-ui-tests |
true |
Skip every *UITests target |
|
skip-testing |
— | Extra -skip-testing identifiers, space separated |
|
| testflight | scheme, team-id |
— | |
branch |
dispatched ref | Any branch/tag/SHA | |
upload |
true |
false = archive + export only |
|
xcode |
.xcode-version, else runner default |
Path or version (27.1, 27) |
|
build-number |
YYMMDD01 |
Numeric only | |
fastlane-lane |
— | Run bundle exec fastlane <lane> instead |
|
signing |
cloud |
manual = your Distribution identity + App Store profiles (docs) |
|
| ai-review | provider |
claude |
claude, codex or gemini |
model |
provider default | e.g. claude-opus-5-5 |
|
runner |
auto |
auto = your Mac's claude-review runner |
|
claude-path, bun-path |
download each run | Point at local installs | |
language |
English |
Language of the review comments | |
| all | runner-label |
macos-xcode / claude-review |
More
- docs/security.md — threat model and hardening
- docs/troubleshooting.md — every pitfall we hit, with fixes
- docs/fastlane.md — using your existing fastlane lanes
- docs/manual-signing.md — company teams without an Admin key
License
MIT
Yorumlar (0)
Yorum birakmak icin giris yap.
Yorum birakSonuc bulunamadi