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.
server/tag_release_test.go +6
| @@ -22,6 +22,8 @@ on: | |||
| 22 | 22 | jobs: | |
| 23 | 23 | release: | |
| 24 | 24 | image: alpine:3.22 | |
| 25 | env: | ||
| 26 | RELEASE_KIND: tool | ||
| 25 | 27 | steps: | |
| 26 | 28 | - name: build | |
| 27 | 29 | run: mkdir -p dist && printf 'release binary\n' > dist/tool-linux | |
| @@ -64,6 +66,10 @@ func TestTagPushBuildsARelease(t *testing.T) { | |||
| 64 | 66 | if len(queued.ReleaseArtifacts) != 1 || queued.ReleaseArtifacts[0] != "dist/*" { | |
| 65 | 67 | t.Fatalf("release artifacts = %v", queued.ReleaseArtifacts) | |
| 66 | 68 | } | |
| 69 | // The job's environment is pinned with everything else and travels with the lease. | ||
| 70 | if queued.Env["RELEASE_KIND"] != "tool" { | ||
| 71 | t.Fatalf("queued env = %v", queued.Env) | ||
| 72 | } | ||
| 67 | 73 | ||
| 68 | 74 | f.runOnce(t) | |
| 69 | 75 | done := f.terminalRun(t) | |