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}) | |