Building a GitHub repo, end to end
This page takes a GitHub repo from nothing to a green hyperspool/ci
check on every push. You install the Hyperspool GitHub App on the repo,
connect it with one command and commit a pipeline file. There are no
tokens to make and no webhook to paste, and the builds run on
Hyperspool’s managed environments, so there are no machines to set up
either. Plan on about ten minutes.
You need an invitation to hyperspool.dev, the
spool CLI (Getting started installs it), and the
right to install apps on the GitHub account that owns the repo. On a
GitHub org, that means an owner of the org.
1. Log the CLI in
Section titled “1. Log the CLI in”Sign in to the console at hyperspool.dev. If you don’t belong to an org yet, the console offers to create one. Create a token under Settings > Tokens, then:
spool login https://api.hyperspool.dev # prompts for the tokenspool context get # the server, org and project in usecontext get prints the server and the scope, as org/project.
Webhooks, repos and runs all belong to that project.
2. Install the GitHub App
Section titled “2. Install the GitHub App”In the console, open Settings > Connections > Repositories and click Install the GitHub App. GitHub asks which account to install it on and which repositories it may see; pick the repo, or several. Then it asks you to authorize the app, which is how Hyperspool checks that the installation you are linking is yours, and sends you back to the console to link the installation to your org.
The app asks for these permissions:
| Permission | Used for |
|---|---|
| Contents: read | cloning the commit being built |
| Commit statuses: read and write | posting the result on the commit |
| Pull requests: read | building pull requests |
| Metadata: read | required by GitHub for every app |
| Organization members: read | checking that whoever links an installation on an organization owns it |
It sends Hyperspool the pushes and pull requests of those repos, and nothing from any other repo. An installation belongs to one Hyperspool org.
3. Connect the repo
Section titled “3. Connect the repo”spool repo connect https://github.com/me/appconnected me/app (github.com) as me-appbuilds: .spool/*.yamlpull requests: nowebhook: githubhandlers: me-app-ci, me-app-statusthrough: the GitHub AppWith no credentials given, repo connect uses the app for any repo it
is installed on. In the console, the repo also shows up under the GitHub
App on the Repositories tab with a Connect button that does the
same.
That one command created a webhook named github, which the app
delivers to, a build handler (me-app-ci) that runs your pipeline for
each push, and a status handler (me-app-status) that reports the
result on the commit. For a private repo, the app gives each job that
clones a token for that one repo, made when the job starts and lasting
at least 45 minutes; a public repo clones without one. CI from a
repo explains each part.
4. Write the pipeline
Section titled “4. Write the pipeline”Commit this as .spool/ci.yaml:
name: ci
jobs: test: steps: - name: test run: | spool checkout src cd src go test ./...What each part is for:
- Each push starts this pipeline as a run of its own, with the pushed
commit as its input:
input.sha,input.refandinput.repo. spool checkoutclones the repo at that commit, with the clone credential the repo was connected with: for a private repo, a token from the app. The job clones the repo itself because it runs wherever an agent picks it up, and each job starts in an empty directory of its own, removed when the job ends.- A step that needs the token itself, to fetch a second repo or call the
GitHub API, can ask for it under
credentials:asgithub-app:me/app.
The job runs in a managed container that has git and Go. A bigger build
can ask for a 16-core VM with requires: [build];
Managed environments lists
what each one has.
Check the file before you commit it:
spool workflow check .spool/ci.yamlok .spool/ci.yaml (pipeline)5. Push and watch it
Section titled “5. Push and watch it”Commit .spool/ci.yaml and push. Within a few seconds the commit on
GitHub shows a pending hyperspool/ci check (“Build running”), then
“Build passed” or “Build failed”, linking to the run in the console.
Each push makes two runs: the build handler’s, which clones the commit and starts the pipeline, and the pipeline’s own. Follow them from the terminal:
spool ls --tag repo:me/app --limit 5 # this repo's buildsspool logs <run> -f # one run's output, livespool run follow <run> # the live terminal viewIn the console, the runs list filters by the same tag, and a run’s page shows its journal, a waterfall and graph view, and live output per step.
If nothing happens, run spool webhook deliveries github (or open
Events in the console, under More). The HANDLERS column says what
each handler did with the push: started, skipped_when when the
filter declined it (a handler for another repo shows that too), or
when_error when the filter failed to evaluate. No delivery at all
means the app isn’t installed on the repo, or the repo isn’t one it may
see. A failure before the pipeline starts, such as a refused clone,
shows on the commit as an error status, “The build could not start”.
The pipeline is read from the pushed commit, so a branch that changes
.spool/ci.yaml is built with its own version, and a change takes
effect on the next push.
To make the check gate merges, add hyperspool/ci as a required status
check in the branch’s protection rules on GitHub. Each .spool/*.yaml
file reports under its own name, so .spool/lint.yaml would add a
hyperspool/lint check.
6. Build pull requests
Section titled “6. Build pull requests”Connect again with --pull-requests. Running repo connect again
updates what is there:
spool repo connect https://github.com/me/app --pull-requestsPull requests now build when they are opened or reopened and on each new
head, from branches of the repo and from forks alike, and get the same
hyperspool/ci status. A new head cancels the build of the one it
replaced. The pipeline sees input.event as pull_request, plus the
pull request’s number and branches.
Who opened the pull request matters:
- A pull request from a branch of the repo, by an owner, member or collaborator, runs trusted, the same as a push.
- Anything else runs untrusted: pull requests from forks, and those from
bots such as a dependency updater. An untrusted run gets no project
secrets, and a job that declares
credentials:fails. - On a public repo, forks build normally. On a private repo the clone
needs a token, which an untrusted run doesn’t get, so a fork’s pull
request gets an
errorstatus. - To trust a bot’s pull requests, run
spool webhook set github --manifest github --trust-actor 'renovate[bot]'.
CI from a repo has the full trust model, the input fields and the status rules.
Choose which pushes build
Section titled “Choose which pushes build”Every push to every branch builds by default. --when takes a CEL
expression over the push and declines the rest before any run starts.
This builds pull requests and pushes to main only, so a branch with an
open pull request isn’t built twice:
spool repo connect https://github.com/me/app --pull-requests \ --when 'has(event.number) || event.ref == "refs/heads/main"'A declined push gets a success status described “skipped”, so a
required check doesn’t wait for it. A top-level if:, paths: or
paths_ignore: in .spool/ci.yaml has no effect on these builds.
CI from a repo lists the
fields, including the changed paths, and
Branch routing covers deciding what runs once a
build has started.
Connect another repo
Section titled “Connect another repo”Give the app the repo first: Add repos or accounts on the
Repositories tab opens the installation’s settings on GitHub, or pick
“All repositories” there once. Then run repo connect for it, or click
its Connect button. A repo the app sees is never built until it is
connected.
Without the GitHub App
Section titled “Without the GitHub App”If you can’t install apps on the account, connect with two fine-grained tokens and a webhook you paste into the repo instead:
-
On GitHub, under Settings > Developer settings > Fine-grained tokens, create a token with Contents: read (for cloning) and one with Commit statuses: read and write (for posting the result), both limited to the repo. Store each as a project credential:
Terminal window spool credential set gh-read --kind bearer # prompts for the tokenspool credential set github-status --kind bearer -
Connect with them. Passing a credential means the app isn’t used, and so does
--no-github-app:Terminal window spool repo connect https://github.com/me/app \--repo-credential gh-read --status-credential github-status -
It prints an ingest URL, a signing secret (shown once) and the page to paste them into. Add the webhook there with content type
application/json, the secret, and the Pushes and Pull requests events.spool webhook deliveries githubshows GitHub’s test delivery arriving.
The pipeline above works unchanged, since spool checkout clones with
gh-read. CI from a repo
covers the webhook in detail, including an org-wide webhook and having
Hyperspool register it for you.
Gitea, Forgejo, arm64 and macOS
Section titled “Gitea, Forgejo, arm64 and macOS”repo connect takes a Gitea or Forgejo URL the same way, with tokens
and a pasted webhook, since the GitHub App covers github.com only;
CI from a repo covers the
differences in tokens, webhooks and trust.
The managed environments are amd64 Linux. To build for arm64 or macOS,
run agents of your own on those machines, each
advertising a capability you name, such as linux-arm64. A matrix then
sends one combination to each:
name: ci
jobs: build: matrix: values: target: - { cap: build, triple: x86_64-unknown-linux-gnu } - { cap: linux-arm64, triple: aarch64-unknown-linux-gnu } - { cap: darwin-arm64, triple: aarch64-apple-darwin } fail_fast: false requires: ["${{ matrix.target.cap }}"] env: TARGET: "${{ matrix.target.triple }}" steps: - name: build run: | spool checkout src cd src make release TARGET="$TARGET"The first combination runs on a managed build VM and the other two wait
for your agents, whose toolchains must be on their PATH. Your agents
also take untrusted work, such as a fork’s pull request on a public
repo: it gets no secrets, but its code runs on your machine.
Work from untrusted events
covers how to set such a machine up.
Where to go next
Section titled “Where to go next”- CI from a repo: how repo builds work, running a pipeline on demand, and handlers of your own.
- Branch routing: different work for different branches.
- Credentials and Secrets: what a job can use, and what an untrusted run gets.
- The tour: runs that wait for a person, sleep through a soak, and promote, all in the run that built the commit.