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.

Add packaging, a CI workflow, and containerised local runs

3b39a84c913cabe15bfa6a60dd176ff2dd28d241 · Keenan Nemetz · 14d ago · parent 30a72490

Add packaging, a CI workflow, and containerised local runs

A v* tag now builds a .deb and a .rpm for amd64 and arm64 with nfpm and attaches them, with checksums, to the release. The package ships the systemd unit, a config at /etc/vole/vole.toml, and a postinstall that creates the service account and prepares the instance that config names; `vole admin init --config` is what makes that possible, and the binary's version is stamped from the tag instead of the source tree.

scripts/ci.sh becomes .vole/workflows/ci.yaml, so `vole workflow run ci` executes exactly what a push runs, and the Markdown link check moves to scripts/check-links.py. Local runs now always execute in the job image and share ci/container with the runner; --no-container is gone, because a run in a different environment proves nothing about CI.

The runner mounts a per-repository namespace at /cache and sets no variable for it: a job opts in through its image's env or the new job-level env, which is pinned with the queue and delivered with the lease. Whole namespaces are evicted least-recently-written first so a module cache is never half-deleted, and contributor branches get their own namespace so an untrusted tip cannot seed the cache a protected branch builds into.

Running the suite in that container found four bugs worth naming: /tmp was mounted noexec, which broke `go test` and `go run`; the read-only root is gone, because images make toolchain directories world-writable deliberately and the mount set is what protects the host; OpenSSH needs a passwd entry for the job's uid; and a lost metadata push reported as "cannot lock ref" was not classified as a race, so concurrent CLI writes gave up on the first collision. That retry now backs off exponentially with jitter.

docs/reference/workflow-reference.md +38−1

@@ -97,6 +97,7 @@ letter. At least one job is required.
97 97 | `image` | string | one of `image` / `image-file` | — | Registry image the job runs in. |
98 98 | `image-file` | string | one of `image` / `image-file` | — | Name of a definition under `.vole/images/`. |
99 99 | `runs-on` | string or list of strings | no | any runner | Runner labels the job requires. |
100 | `env` | mapping | no | none | Environment every step of the job runs with. |
100 101 | `steps` | list | yes | — | Commands the job runs. At least one is required. |
101 102 | `artifacts` | list | no | none | Files the job publishes. |
102 103 | `pages` | mapping | no | none | Directory published as the repository's website. |
@@ -166,6 +167,39 @@ with a letter or digit.
166 167 See [Runners § Start a runner](../runner.md#start-a-runner) for how a machine
167 168 advertises labels.
168 169
170 ### `env`
171
172 Environment variables for every step of the job, on top of the image's own. Later
173 values win, so a job can change something its image set:
174
175 ```yaml
176 jobs:
177 test:
178 image: golang:1.27-bookworm
179 env:
180 GOCACHE: /cache/go-build
181 GOFLAGS: "-mod=readonly"
182 steps:
183 - run: go test ./...
184 ```
185
186 | Constraint | Value |
187 |---|---|
188 | Maximum entries | 64 |
189 | Key syntax | `^[A-Za-z_][A-Za-z0-9_]*$` |
190 | Maximum value size | 8 KiB |
191 | Line endings | Values must be one line; `CR`/`LF` are rejected |
192
193 `CI`, `VOLE_CI`, `GIT_TERMINAL_PROMPT`, `HOME` and `TMPDIR` are set by the runner and
194 cannot be replaced: the sandbox depends on them, so the push that tries is refused
195 rather than silently disagreeing with it.
196
197 This is how a job opts in to something the runner provides but does not decide for
198 it. The job cache is the one that matters today: the runner mounts a directory at
199 `/cache` and sets no variable for it, so pointing `GOCACHE` (or `CARGO_HOME`, or
200 `npm_config_cache`) there is what makes the cache shared. See
201 [Runners § Caching](../runner.md#caching).
202
169 203 ### `steps`
170 204
171 205 A non-empty list of shell steps, at most 100. Steps run in order. A step that exits
@@ -261,7 +295,7 @@ longer the newest commit on its branch. See [Workflows § Publish a website](../
261 295 - The commit, workflow blob, and image definition are pinned when a run is queued.
262 296 Later edits do not change that run.
263 297 - `vole workflow check` and `vole workflow run` use the same validation as a push.
264 A local run ignores `on.push`, `runs-on`, and the image.
298 A local run executes in the job's image and ignores `on.push` and `runs-on`.
265 299
266 300 ## Complete example
267 301
@@ -275,6 +309,9 @@ jobs:
275 309 test:
276 310 runs-on: [linux, arm64]
277 311 image: golang:1.27-bookworm@sha256:...
312 env:
313 GOCACHE: /cache/go-build
314 GOMODCACHE: /cache/go-mod
278 315 steps:
279 316 - name: unit tests
280 317 run: go test ./...