Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Document runners, and check the three files that describe one agree

e21f6c5e26cc49d5a08d337cb1c7b70d373a8cce · Keenan Nemetz · 3d ago · parent d0d4239f

Document runners, and check the three files that describe one agree

A runner is a second decision after the forge, not part of its first boot:
it executes repository code, it can read every secret a job declares, and
the allowlist that lets it take work is per repository. The guide says what
the deployment creates, how to set one up on a machine you already have, the
one step only an admin can do, what to know before giving it a repository,
and how to upgrade it.

render.sh now renders either first-boot file, taking its placeholder list
from the render line it uses, so the checker reads the same line instead of
keeping a second copy.

check-deploy.py renders both templates, requires each one's promised files
with literal modes, and holds the runner's three descriptions together: the
template writes the arguments, the unit reads them, and the package ships
the example an operator copies. A name that drifts leaves a machine enabled
and idle, which looks exactly like a runner that is working.

docs/deploy/runner.md +158

@@ -0,0 +1,158 @@
1 # CI runners
2
3 A runner is a machine that leases jobs from your instance over SSH, runs each job
4 in a container, and reports the result. Vole does not build on the forge's own
5 server: the machine that runs repository code is a machine you chose, with the OCI
6 runtime you installed on it.
7
8 The [deployment guides](README.md) can create one for you, or you can set one up
9 on a machine you already have. Either way there is one step only an admin can do,
10 and it is per repository.
11
12 ## Let the deployment create one
13
14 Each Terraform configuration takes `-var runner=true` and creates a second machine
15 next to the forge: rootless Podman, an unprivileged `ci-runner` account, a pinned
16 host key, and `vole-runner.service` enabled and waiting for work.
17
18 ```sh
19 cd deploy/terraform/hetzner
20 terraform apply \
21 -var domain=forge.example.com \
22 -var runner=true \
23 -var "admin_ssh_public_key=$(cat ~/.ssh/id_ed25519.pub)" \
24 -var vole_package_url=https://example.com/r/vole/releases/v0.2.0/artifacts/vole_0.2.0-1_amd64.deb
25
26 terraform output runner_setup
27 ```
28
29 `runner_setup` prints the commands in the next section, filled in with the
30 addresses this apply created.
31
32 | Setting | Default | What it does |
33 |---|---|---|
34 | `runner` | `false` | Create the second machine |
35 | `runner_type` | same size as the forge | Server type or droplet size. Builds want memory; raise this before the forge's own |
36 | `runner_labels` | `["linux"]` | What this machine advertises. A job with `runs-on` only comes to a runner carrying every label it asks for |
37 | `runner_network` | `bridge` | Whether a job that asks for a network gets one. A job that fetches dependencies needs this; `none` means such a job waits for another runner instead |
38 | `runner_disk_gb` | `40` | AWS only. Container images and caches fill this, not the repositories |
39
40 The runner is not backed up. Its checkouts and caches are copies of what the
41 repositories already have, and everything it produces is stored by the instance.
42
43 ## On a machine you already have
44
45 A runner needs an OCI runtime — rootless Podman is preferred, because membership
46 in the Docker group is effectively root on the host — and it needs to reach the
47 instance's SSH port.
48
49 ```sh
50 # Render the runner's first-boot file, then paste it into the provider's user
51 # data field, or read it first and run the steps by hand.
52 cd deploy/cloud-init
53 FORGE_HOST=forge.example.com \
54 VOLE_PACKAGE_URL=https://example.com/r/vole/releases/v0.2.0/artifacts/vole_0.2.0-1_amd64.deb \
55 ./render.sh runner > runner-user-data.yaml
56 ```
57
58 On a machine you configure yourself, the package carries the unit, so it is three
59 steps: install it, set up the account the runtime needs, and fill in the
60 arguments.
61
62 ```sh
63 sudo apt install ./vole_0.2.0-1_amd64.deb # dnf on Fedora and RHEL
64 sudo useradd --create-home --home-dir /var/lib/vole-runner --shell /usr/sbin/nologin ci-runner
65 sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 ci-runner
66 sudo apt install podman slirp4netns fuse-overlayfs uidmap
67 sudo install -d -o ci-runner -g ci-runner -m 0700 /var/lib/vole-runner/.ssh
68 sudo -u ci-runner ssh-keygen -q -t ed25519 -N '' -f /var/lib/vole-runner/.ssh/id_ed25519
69
70 # Pin the instance's host key. A runner executes repository code, so a host that
71 # can answer in the instance's place could send it any job.
72 ssh-keyscan -p 2222 git@forge.example.com | sudo tee /etc/vole/runner_known_hosts
73 ```
74
75 Then put the arguments in `/etc/vole/runner.env`. The package ships an example
76 with each option explained:
77
78 ```sh
79 sudo tee /etc/vole/runner.env >/dev/null <<'EOF'
80 VOLE_RUNNER_ARGS=--remote ssh://git@forge.example.com:2222 --container-runtime podman --container-network bridge --dir /var/lib/vole-runner --known-hosts /etc/vole/runner_known_hosts --label linux
81 EOF
82 sudo systemctl daemon-reload
83 sudo systemctl enable --now vole-runner
84 ```
85
86 `systemctl status vole-runner` and `journalctl -u vole-runner` are where a runner
87 that is not taking work says why.
88
89 ## Register the runner, then allow it on a repository
90
91 The runner's key is not an admin key. It becomes a bot account, and the
92 allowlist decides what it may build:
93
94 ```sh
95 # The public key is on the runner; this copies it to the instance as a new bot.
96 ssh root@<runner-address> cat /var/lib/vole-runner/.ssh/id_ed25519.pub \
97 | vole admin --remote ssh://git@forge.example.com:2222 \
98 account create ci-runner-1 --bot --key -
99
100 # Per repository: let it take jobs, and give it read so it can clone.
101 vole admin --remote ssh://git@forge.example.com:2222 acl runners add demo ci-runner-1
102 vole admin --remote ssh://git@forge.example.com:2222 acl set-role demo ci-runner-1 read
103 ```
104
105 This is the part that cannot happen at first boot: repositories do not exist yet,
106 and the allowlist is per repository. Add each new repository to it as you create
107 it, or the runner will sit idle while its jobs wait in the queue.
108
109 A bot cannot sign in to the browser and cannot be an instance admin. That second
110 part matters: admins count as trusted reporters, so a bot that was an admin would
111 make every result count whatever the allowlist said.
112
113 ## Check that it works
114
115 - The Admin page's **Runners** tab (`/admin?tab=runners`) shows what is connected,
116 what each runner is building, and how much is queued.
117 - Push a commit on a branch with a workflow. The run page shows which runner took
118 it and whether the result counts.
119 - A run that executed on a runner outside the allowlist still exists and still
120 says so; it just does not count for the branch.
121
122 ## What to know before you give it a repository
123
124 **A runner can read every secret its jobs declare.** That is what running the job
125 means: the values arrive on the machine. If the machine is not yours, the secrets
126 are not either. This is also why an instance admin's machine should not be a
127 runner for a repository whose secrets it should not see.
128
129 **The allowlist is the control, not the container.** A job that escapes its
130 container finds a machine that can reach the instance and the internet. Keep the
131 runner on its own machine, run it as an unprivileged account, and give it no
132 credentials except the one key.
133
134 **Network is a per-job decision this machine permits.** A `bridge` runner still
135 gives a job that asks for none no network. What it cannot do is take the choice
136 away from a job, so a workflow you approve for a contributor branch is a workflow
137 that may reach the internet with whatever it can read.
138
139 **Disk fills where builds happen.** Container images, the `/cache` mount, and the
140 saved checkouts live under `--dir`. `--cache-size` (5 GiB) and `--git-cache-size`
141 (10 GiB) bound the last two; the images are the runtime's own storage, so size the
142 machine for them.
143
144 ## Upgrading
145
146 The runner is the same package as the instance, and the two talk over a versioned
147 protocol. Upgrade the instance first, then the runner machines:
148
149 ```sh
150 sudo apt install ./vole-<new-version>.deb
151 sudo systemctl restart vole-runner
152 ```
153
154 ## Next
155
156 - [Runners reference](../runner.md) — every option, the container defaults, caching,
157 and how a lease is reported
158 - [Workflows](../workflows.md) — what a job asks a runner for, including `runs-on`