Self-host a single host

Run the controller and the agent on one machine. You need no ushr.io account. The controller and the agent send no data to ushr.io.

1. Write the controller config

The controller service reads ~/.config/ushr/controller.yaml. Create it before you run setup.

version: "1"
listen: 127.0.0.1:7080
token: ""            # empty = no auth; the controller then binds loopback only
policy:
  type: priority
  aging:
    boost_per_minute: 1

With an empty token, the controller refuses a non-loopback listen address. Set a token in both files if agents connect from other machines.

2. Create the GitHub App

ushr setup --org YOUR_ORG
  • It writes a default ~/.config/ushr/agent.yaml if none exists. That config points the agent at http://127.0.0.1:7080.
  • It opens a local page that sends an App manifest to GitHub. You confirm the App on GitHub.
  • It writes the App private key to ~/.secrets and opens the App install page.
  • It adds the org to the orgs list in agent.yaml.
  • It installs and starts the ushr-controller and ushr-agent services. The controller service starts only when controller_url is a loopback address.

Personal accounts have no org-level runners. Use --repo OWNER/REPO instead of --org.

Flags

--orgGitHub organisation to create the App in (org-level runners).
--repoowner/repo to create the App for (repo-level runners). Use exactly one of --org or --repo.
--priorityScheduling priority for this target. Higher wins. Default 100.
--key-dirDirectory for the App private key. Default ~/.secrets.
--configAgent config to record the App in. Default ~/.config/ushr/agent.yaml.
-yAnswer yes to prompts. If the scope already has an App, -y keeps it.
If the scope already has an App, setup asks before it creates a second one. Every run of the manifest flow registers a real GitHub App.

The agent config

After setup, a macOS agent.yaml looks like this:

version: "1"
controller_url: http://127.0.0.1:7080
token: ""
name: mac-runner-1
labels:
  - self-hosted
  - macOS
  - ARM64

driver:
  type: tart
  image: paddo-runner-mac
  capacity: 2

orgs:
  - name: YOUR_ORG
    app_id: 123456
    private_key_path: /Users/you/.secrets/ushr-1a2b3c-YOUR_ORG.pem
    priority: 100

source:
  type: poll
  interval: 30s
controller_urlWhere the agent polls for work.
tokenBearer token. Must match token in controller.yaml.
nameAgent name. Defaults to the host name.
labelsLabels this host serves. Jobs match on runs-on.
orgs[].reposOptional. Poll only these repo names. Use it on large installations to stay under the GitHub API rate limit.
orgs[].runner_group_idOptional. GitHub runner group for JIT runners. Default 1 (the Default group).
repos[]Per-repo targets: owner, repo, app_id, private_key_path, priority.
source.typepoll or scaleset.
source.intervalHow often the poll source checks GitHub. Example 30s.

Runner scale sets

With source.type: scaleset, GitHub sends desired runner counts and the agent does not poll. Declare the sets under each org. The set name is also the runs-on label.

orgs:
  - name: YOUR_ORG
    app_id: 123456
    private_key_path: /Users/you/.secrets/ushr-1a2b3c-YOUR_ORG.pem
    scale_sets:
      - name: my-scale-set
        max_runners: 8
source:
  type: scaleset

Set max_runners to the capacity of the agents that serve the set.

next Target your workflows →