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 5: LFS objects, over SSH for writes and the proxy for reads

64ac5071a173e4e9ce8bc8d7170f9b0411bee52a · Keenan Nemetz · 17d ago · parent 35eaeccf

Phase 5: LFS objects, over SSH for writes and the proxy for reads

Large files leave the git object database and land in one content-addressed
store per instance. Reads are anonymous like clone; writes ride the SSH key
that already authenticates the push, so no token exists anywhere.

- internal/lfs: sha256-addressed store that verifies content and size before
  an object becomes visible; parser for the pointer format a client writes
- HTTP: POST .../info/lfs/objects/batch for downloads, GET .../objects/<oid>
  with the object id as ETag; upload over HTTP is refused and names SSH
- SSH: git-lfs-transfer speaking the pure SSH protocol (pkt-line), so
  `git lfs push` needs nothing but the key at the handshake
- pre-receive: a pointer whose object is missing, or whose size disagrees
  with the stored object, fails the push that introduced it; a binary over
  lfs_warn_bytes is accepted with a `git lfs track` suggestion. Checks run
  before the protected-branch gate, because an admin's approval bypass is
  about review, not about whether the commit can be checked out
- runs: a finished log becomes a content-addressed object in the same write
  that records the status, served at /r/<repo>/runs/<id>/log
- vole gc [--dry-run] [--grace]: marks pointer targets from every ref in
  every repository plus archived logs, sweeps the rest. Mark-and-sweep
  rather than reference counts: a counter that is one too low deletes an
  artifact, a walk over the refs cannot be wrong about what is reachable

internal/sshserver/sshserver.go +15

@@ -27,6 +27,7 @@ import (
27 27 "golang.org/x/crypto/ssh"
28 28
29 29 "github.com/rodent-software/vole/internal/identity"
30 "github.com/rodent-software/vole/internal/lfs"
30 31 "github.com/rodent-software/vole/internal/repo"
31 32 "github.com/rodent-software/vole/internal/runs"
32 33 )
@@ -47,6 +48,10 @@ type Config struct {
47 48 RunsDB string
48 49 // Runs is the CI queue, served to runners over the vole-runner verb.
49 50 Runs *runs.Store
51 // LFS is the object store behind git-lfs-transfer.
52 LFS *lfs.Store
53 // LFSWarnBytes is handed to pre-receive as the threshold for suggesting LFS.
54 LFSWarnBytes int64
50 55 // Lease is how long a runner may hold a job without a heartbeat.
51 56 Lease time.Duration
52 57 }
@@ -200,6 +205,13 @@ func (s *Server) exec(ch ssh.Channel, req *ssh.Request, command, user, accountID
200 205 exit(ch, 0)
201 206 return
202 207 }
208 // git-lfs speaks its own protocol over an SSH remote: the client runs
209 // `git-lfs-transfer <repo> upload|download` on the same connection family as
210 // the push. No token changes hands; the key is the identity.
211 if len(argv) == 3 && argv[0] == "git-lfs-transfer" {
212 s.serveLFS(ch, req, argv[1], argv[2], user, accountID)
213 return
214 }
203 215 if len(argv) != 2 {
204 216 refuse("only git-upload-pack and git-receive-pack are available")
205 217 return
@@ -267,6 +279,9 @@ func (s *Server) exec(ch ssh.Channel, req *ssh.Request, command, user, accountID
267 279 "VOLE_KEY_ID="+fp,
268 280 "VOLE_REPO="+name,
269 281 "VOLE_MAX_BLOB="+strconv.FormatInt(s.cfg.MaxBlobBytes, 10),
282 "VOLE_LFS_DIR="+lfsRoot(s.cfg.LFS),
283 "VOLE_LFS_MAX="+strconv.FormatInt(lfsMax(s.cfg.LFS), 10),
284 "VOLE_LFS_WARN="+strconv.FormatInt(s.cfg.LFSWarnBytes, 10),
270 285 "VOLE_AUDIT="+s.cfg.AuditPath,
271 286 "VOLE_SYSTEM_REPO="+s.cfg.Identity.Dir(),
272 287 "VOLE_SPOOL="+s.cfg.SpoolDir,