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/runner.go +28−4

@@ -110,10 +110,34 @@ func (s *Server) serveRunner(ch ssh.Channel, user, accountID string) {
110 110 _ = enc.Encode(runnerReply{Op: "heartbeat", OK: ok})
111 111
112 112 case "finish":
113 ok, err := cfg.Runs.Finish(req.Run, owner, req.Exit, req.Log, time.Now().UTC())
114 if err != nil {
115 _ = enc.Encode(runnerReply{Op: "finish", Error: err.Error()})
116 continue
113 // A finished log never changes again, so it becomes a content-addressed
114 // object and the queue row stops carrying text. The object is written
115 // first: if this runner has lost the lease, the row is left alone and
116 // the object is simply swept later.
117 ok, err := false, error(nil)
118 if cfg.LFS != nil {
119 text := ""
120 if prev, lerr := cfg.Runs.Log(req.Run); lerr == nil {
121 text = prev + req.Log
122 }
123 if text != "" {
124 oid, perr := cfg.LFS.PutBytes([]byte(text))
125 if perr != nil {
126 cfg.Log.Warn("run log store", "run", req.Run, "err", perr)
127 } else {
128 ok, err = cfg.Runs.FinishAndArchive(req.Run, owner, req.Exit, oid, time.Now().UTC())
129 }
130 }
131 }
132 if cfg.LFS == nil || err != nil || !ok {
133 if err == nil && !ok {
134 // Either no store, or an empty log: fall back to the plain finish.
135 ok, err = cfg.Runs.Finish(req.Run, owner, req.Exit, req.Log, time.Now().UTC())
136 }
137 if err != nil {
138 _ = enc.Encode(runnerReply{Op: "finish", Error: err.Error()})
139 continue
140 }
117 141 }
118 142 cfg.Log.Info("run finished", "user", user, "run", req.Run, "exit", req.Exit, "recorded", ok)
119 143 _ = enc.Encode(runnerReply{Op: "finish", OK: ok})