Wire in your own app¶
You have an app repository, a Mac, and a few devices. These four pages take you from a cloned collector to your own nightly running on your own hardware, with a verdict that can block a pull request.
What is proven, and what is not
ci-enqueue and ci-upload are verified standalone: they upload, they
enqueue, they poll to a verdict and they exit 0 or 1, and the artifact and
"latest" resolution behind them are covered end to end by the collector's
test suite.
The GitHub Actions path has never run on a real GitHub runner. The workflow templates below document a contract that is implemented on the collector side and installed nowhere. Nothing in any app repository references the fleet today. Treat the YAML as a starting point you will need to debug once, not as something known to work unmodified.
Commit statuses are likewise built and dark — see below.
| Your first job | curl, then the CI script standalone, then the dashboard. No CI involved. |
| Publish on merge | Every merge publishes a build the nightly can ask for by name. |
| Add your app | Flows, suites, visual baselines, and targeting the right devices. |
| Add a workload | Teach the fleet something new without a collector release. |
| Cookbook | One complete job spec per workload, validated in CI. |
The two paths are independent¶
Most apps want both, but they answer different questions and neither depends on the other.
Publishing a build happens on merge, tests nothing, and waits for no device.
This is the half nobody thinks about, and it is what makes a nightly meaningful:
a schedule that pins a literal sha256 tests that build forever — one of these
spent six days guarding an APK older than the code it was meant to guard. A
schedule that asks for "sha256": "latest" tests whatever was published most
recently and never has to be edited again.
Testing a pull request enqueues a job against real hardware and blocks on the verdict.
Coupling them means a busy shelf silently stops builds from being published, which is why they are two workflows rather than one.
Commit statuses¶
report_to.github_status: "owner/repo@sha" records a row in status_reports
for every job that asks for one, and posts a real commit status only when
armed on the collector with both:
Unarmed — the default — rows accumulate with posted=0 and are visible on the
dashboard, so you can see exactly what would have been posted before anything
reaches GitHub. GET /api/status-reports is the audit trail either way.
Arming it makes a red nightly block a pull request. That is the point, and it is also a decision with consequences for everyone who pushes, so it stays off until somebody chooses it deliberately.
Note that ci-enqueue already exits non-zero on a red suite, so the check fails
whether or not statuses are armed. Arming adds the status on the nightly's
commit, which is the part a scheduled run cannot get any other way.
What the runner needs to reach¶
The collector is LAN or tailnet only and has no authentication, so a
GitHub-hosted runner cannot reach it and should not be able to. The workflows
below assume a self-hosted runner on the same tailnet, with the collector's
address in a FLEET_COLLECTOR_URL secret.