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.

Phase 7 slice 3: artifacts attach over SSH and are one pointer deep

85cc89fe9eafd1793702d143ea2eb41db635fbe2 · Keenan Nemetz · 17d ago · parent c56911e2

Phase 7 slice 3: artifacts attach over SSH and are one pointer deep

A released binary is a pointer blob at releases/<tag>/<name> on the metadata
branch. That is the whole record, and it was chosen because the machinery that
already exists does the work: gc marks LFS objects named by pointers in any
tree reachable from any ref (refs/heads/vole is one), and pre-receive's pointer
hygiene already refuses a pointer whose bytes are missing. A JSON manifest of
sha256 strings would have parsed as no pointer and been swept once the grace
period passed.

- cmd/vole attach speaks the pure-SSH LFS transfer protocol itself (version,
  batch, put-object, verify-object), so bytes move over the key that already
  writes: no token, no second endpoint, no new write surface
- upload happens before the pointer commit, because the reverse order publishes
  a download that 404s and receive-pack cannot see bytes that were never in the
  pack. A test pushes a pointer with no object and asserts the push is refused
- the artifact list is read from the pointer tree (internal/releases), which the
  metadata validator now knows about; a file under releases/ that is not a
  pointer is refused at push instead of silently unservable
- downloads: /r/<repo>/releases/<tag>/artifacts/<name> out of the store, ETag =
  object id, always Content-Disposition: attachment with nosniff, ServeContent
  for Range and 304, clone read rule so private artifacts are 404
- the "not on this server" hook message now names vole release attach as well as
  git lfs push, because it fires for both now

Measured end to end through the binary: two 2.9 MiB files attached over SSH in
390 ms, the identical second file deduplicating to one object; both downloaded
in 13.9 ms with matching sha256, 304 on If-None-Match, 206 on Range; vole gc
--dry-run reports the published artifact reachable and removes nothing.

The planned releases/<tag>.json record and drafts are dropped with it: the
pointer directory is the artifact list and being in the ref is what published
means, so the file would only have duplicated the tag.

README.md +32−8

@@ -52,10 +52,12 @@ Working today:
52 52 (`git-lfs-transfer`); downloads come through the HTTP clone URL, anonymously for a
53 53 public repository. A pointer whose object is missing fails the push that introduced
54 54 it, and `vole gc` sweeps what nothing reaches
55 - Releases are tags: the tag message is the notes and `.tar` / `.tar.gz` are generated
56 from the tagged tree per request, with an `ETag` from the tag object and a `304` for
57 repeat downloads. `vole release create` writes the annotated tag; `git tag -a` does
58 the same thing without the CLI
55 - Releases are tags: the tag message is the notes, `.tar` / `.tar.gz` are generated from
56 the tagged tree per request (with an `ETag` from the tag object and a `304` for repeat
57 downloads), and binaries travel as LFS pointers on the `vole` branch — `vole release
58 attach` uploads them over the same SSH key and the store keeps one copy of identical
59 bytes. `vole release create` writes the annotated tag; `git tag -a` does the same thing
60 without the CLI
59 61 - Repo create / rename / delete from the CLI
60 62 - A derived SQLite index that can be deleted and rebuilt from the repos
61 63 - Static sites: a repository's `pages` branch is served as a website at
@@ -64,8 +66,8 @@ Working today:
64 66 - An instance ed25519 key generated at first boot (used for metadata signing in
65 67 Phase 7)
66 68
67 Not yet: release artifacts (binaries attached to a release), workflow artifact uploads,
68 LFS locking.
69 Not yet: workflow artifact uploads, LFS locking, and release immutability (a tag that has
70 artifacts can still be moved until Phase 7's last slice lands).
69 71 Signature verification is deferred,
70 72 not planned: the SSH handshake already establishes who pushed, so signing approvals
71 73 would restate it for a verifier this deployment does not have (see the roadmap).
@@ -253,8 +255,30 @@ tag object id, `If-None-Match` gets a `304`, and the same tag yields the same by
253 255 every instance. Measured on an 11.6 MiB, 2,566-file repository: 54 ms for `.tar`, 218 ms
254 256 for `.tar.gz`, and every extracted file checks out against the tree. A repository that
255 257 tracks files with Git LFS gets a warning on the page, because `git archive` copies
256 pointers rather than their contents. Binaries attached to a release are not built yet
257 (see the roadmap).
258 pointers rather than their contents.
259
260 Binaries attach to the release the same way large files move everywhere else — over SSH,
261 into the content-addressed store, with a pointer committed for them:
262
263 ```sh
264 vole release attach v1.0.0 dist/vole-linux-amd64 dist/vole-darwin-arm64
265 curl -O http://host/r/demo/releases/v1.0.0/artifacts/vole-linux-amd64
266 sha256sum vole-linux-amd64
267 ```
268
269 Attaching byte-identical files twice stores one object and transfers nothing the second
270 time. Downloads carry the object id as `ETag` (so `304` works), `Content-Disposition:
271 attachment` with `nosniff` (an artifact never renders on the forge's origin), and Range
272 support, so a 100 MiB download resumes. The read rule is the clone rule: artifacts of a
273 private repository are `404`. Attach *after* pushing the tag, not before: the pointer is
274 what publishes the bytes, and a pointer whose object was never uploaded is refused by
275 `pre-receive` rather than advertised as a download.
276
277 Artifacts are reachable by `vole gc` because the pointers live on the `vole` branch:
278
279 ```sh
280 vole gc --dry-run # the published artifact is a reachable root
281 ```
258 282
259 283 ## Hosting a site
260 284