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.

scripts/ci.sh −47

@@ -1,47 +0,0 @@
1 #!/bin/sh
2 # Everything that must pass before a push. Keep this the single entry point so CI
3 # and humans cannot drift apart.
4 set -eu
5 cd "$(dirname "$0")/.."
6
7 fail() { echo "FAIL: $*" >&2; exit 1; }
8
9 echo "== gofmt =="
10 unformatted=$(gofmt -l . 2>/dev/null | grep -v '^$' || true)
11 [ -z "$unformatted" ] || fail "unformatted files:
12 $unformatted"
13
14 echo "== go vet =="
15 go vet ./... || fail "vet"
16
17 echo "== build =="
18 go build ./... || fail "build"
19
20 echo "== markdown links =="
21 python3 - <<'EOF' || fail "markdown links"
22 import os, re, sys
23 bad = []
24 for root, dirs, files in os.walk('.'):
25 dirs[:] = [d for d in dirs if d not in ('.git', 'data')]
26 for f in files:
27 if not f.endswith('.md'):
28 continue
29 p = os.path.join(root, f)
30 text = open(p, encoding='utf-8').read()
31 for m in re.finditer(r'\]\(([^)#][^)]*)\)', text):
32 target = m.group(1)
33 if target.startswith(('http://', 'https://', 'mailto:')):
34 continue
35 path = os.path.normpath(os.path.join(root, target.split('#')[0]))
36 if not os.path.exists(path):
37 bad.append(f"{p}: {target}")
38 if bad:
39 print("\n".join(bad))
40 sys.exit(1)
41 EOF
42
43 echo "== tests (race) =="
44 go test -race -count=1 ./... || fail "tests"
45
46 echo
47 echo "all checks passed"