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/packaging.md +102
| @@ -0,0 +1,102 @@ | |||
| 1 | # Packages | ||
| 2 | |||
| 3 | A release can be installed with the system package manager instead of unpacking a | ||
| 4 | tarball. Pushing an annotated tag builds the `.deb` and the `.rpm` for `amd64` and | ||
| 5 | `arm64` and attaches them to the release page; nothing is published anywhere else. | ||
| 6 | |||
| 7 | ```sh | ||
| 8 | dpkg -i vole_0.2.0-1_amd64.deb # Debian, Ubuntu | ||
| 9 | dnf install ./vole-0.2.0-1.x86_64.rpm # Fedora, RHEL, Rocky | ||
| 10 | ``` | ||
| 11 | |||
| 12 | There is no apt or dnf repository and no signature on these files yet. Each release | ||
| 13 | page also carries `SHA256SUMS`; compare it before installing. | ||
| 14 | |||
| 15 | ## What the package installs | ||
| 16 | |||
| 17 | | Path | What it is | | ||
| 18 | |---|---| | ||
| 19 | | `/usr/bin/vole` | The binary, built from the tag with `vole version` set to it | | ||
| 20 | | `/usr/lib/systemd/system/vole.service` | The unit, with hardening and a `/var/lib/vole` state directory | | ||
| 21 | | `/etc/vole/vole.toml` | Configuration, marked as a conffile so your edits survive upgrades | | ||
| 22 | | `/usr/share/doc/vole/` | `LICENSE` and `NOTICE` | | ||
| 23 | | `/var/lib/vole/data` | The instance: repositories, databases, stores, host key, audit log | | ||
| 24 | |||
| 25 | The package creates the `vole` service account and runs `vole admin init` against | ||
| 26 | the shipped config. It does not create an administrator and does not enable the | ||
| 27 | service: who may administer the instance is the operator's decision, not the | ||
| 28 | package's. | ||
| 29 | |||
| 30 | The shipped config listens on `0.0.0.0:2222` for SSH and `127.0.0.1:8080` for the | ||
| 31 | web. Open 2222 in the firewall, terminate TLS in a proxy in front of 8080, and set | ||
| 32 | `public_url` before anyone signs in with a passkey. See | ||
| 33 | [Configuration § Running behind a proxy](configuration.md#running-behind-a-proxy). | ||
| 34 | |||
| 35 | ## First steps after install | ||
| 36 | |||
| 37 | ```sh | ||
| 38 | vole admin --config /etc/vole/vole.toml user create you --key ~/.ssh/id_ed25519.pub | ||
| 39 | vole admin --config /etc/vole/vole.toml repo create demo --owner you | ||
| 40 | vole admin --config /etc/vole/vole.toml acl default demo read | ||
| 41 | systemctl enable --now vole | ||
| 42 | ``` | ||
| 43 | |||
| 44 | Run admin commands as the `vole` user or with `--config /etc/vole/vole.toml`; the | ||
| 45 | service account owns the files they write. A command that writes as the wrong user | ||
| 46 | leaves state the service cannot open, so prefer: | ||
| 47 | |||
| 48 | ```sh | ||
| 49 | sudo -u vole vole admin --config /etc/vole/vole.toml user list | ||
| 50 | ``` | ||
| 51 | |||
| 52 | ## Upgrading | ||
| 53 | |||
| 54 | `users.db` is migrated in place the first time a newer binary opens it, and the | ||
| 55 | migration cannot be rolled back. Back up before upgrading: | ||
| 56 | |||
| 57 | ```sh | ||
| 58 | sudo -u vole vole admin --config /etc/vole/vole.toml user backup /var/lib/vole/users.db.bak | ||
| 59 | sudo cp /var/lib/vole/data/ssh_host_ed25519_key /var/lib/vole/ssh_host_ed25519_key.bak | ||
| 60 | ``` | ||
| 61 | |||
| 62 | Then install the new package. See [Operations](operations.md) for what else is | ||
| 63 | worth copying. | ||
| 64 | |||
| 65 | ## How the release is built | ||
| 66 | |||
| 67 | `.vole/workflows/release.yaml` runs on a `v*` tag and does three things: it runs | ||
| 68 | the tests, it cross-compiles the binary for both architectures, and it packages | ||
| 69 | each one with [nfpm](https://nfpm.goreleaser.com) from `packaging/nfpm.yaml`. The | ||
| 70 | `.deb`, the `.rpm`, the plain binaries, and `SHA256SUMS` are attached to the tag's | ||
| 71 | release with `release: true`, so they appear on the release page like any other | ||
| 72 | attached file. | ||
| 73 | |||
| 74 | Two requirements on the runner: | ||
| 75 | |||
| 76 | - **Network.** `go build` fetches modules, and the packaging step fetches a pinned | ||
| 77 | `nfpm`, and a job container has none unless the runner was started with | ||
| 78 | `--container-network bridge`. The image build already needs the network; it | ||
| 79 | installs `git` (see `.vole/images/release.yaml`). | ||
| 80 | - **Nothing else.** The binaries are cross-compiled, so any runner that can start a | ||
| 81 | Linux container produces the same four packages. | ||
| 82 | |||
| 83 | Reproduce a release locally without a runner, from the tag: | ||
| 84 | |||
| 85 | ```sh | ||
| 86 | git switch --detach v0.2.0 | ||
| 87 | go run ./cmd/vole workflow run release | ||
| 88 | ``` | ||
| 89 | |||
| 90 | That runs the steps in your working tree as a check of the commands. It does not | ||
| 91 | publish the artifacts. The packaging step fetches nfpm itself with a pinned | ||
| 92 | `go run`, so there is no tool to install first — only Go and the network. | ||
| 93 | |||
| 94 | The version comes from `git describe --tags --exact-match`, so a checkout with no | ||
| 95 | tag stops before building anything. | ||
| 96 | |||
| 97 | ## The package definition is checked on every push | ||
| 98 | |||
| 99 | The CI workflow (`.vole/workflows/ci.yaml`) builds one architecture and packages | ||
| 100 | it with the same `packaging/nfpm.yaml` on every branch push. A broken definition, | ||
| 101 | install path, or unit file therefore fails the push that broke it, instead of the | ||
| 102 | tag that cannot be rebuilt. | ||